/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var BotCar = Container.expand(function (carIndex) { var self = Container.call(this); // Use different car assets based on car index var carAssetName = 'botCar' + (carIndex % 8 + 1); var carGraphics = self.attachAsset(carAssetName, { anchorX: 0.5, anchorY: 0.5 }); // Create more varied bot speeds based on car index for distinct racing styles var speedVariations = [14, // Fast aggressive bot 11, // Medium-fast bot 16, // Very fast bot 9, // Slower cautious bot 13, // Medium bot 18, // Fastest bot 8, // Slowest bot 15 // Fast bot ]; self.baseSpeed = speedVariations[carIndex % 8]; // Assign specific speed based on car index self.currentSpeed = 0; // Start stationary until race begins self.lanePosition = Math.floor(Math.random() * 3); // 0=left, 1=center, 2=right self.targetX = 400 + self.lanePosition * 600; // Lane positions self.aiTimer = 0; self.carNumber = carIndex + 1; self.isChangingLanes = false; // Track lane changing state self.collisionCooldown = 0; // Prevent frequent collisions // Bot cars now use their default asset colors without tinting self.lapCount = 1; self.raceDistance = 0; self.hasFinished = false; self.update = function () { // Only move if race has started if (!raceStarted) { return; } // AI behavior - change lanes occasionally self.aiTimer++; if (self.aiTimer > 120 && Math.random() < 0.03 && !self.isChangingLanes) { // Every 2 seconds, 3% chance, only if not already changing lanes self.aiTimer = 0; var newLane = Math.floor(Math.random() * 3); if (newLane !== self.lanePosition) { self.lanePosition = newLane; self.targetX = 400 + self.lanePosition * 600; self.isChangingLanes = true; // Smooth lane change with tween and rotation var direction = self.targetX > self.x ? 1 : -1; // Add slight rotation during lane change for stability (reduced rotation) tween(carGraphics, { rotation: direction * 0.05 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { // Return to straight after lane change (faster return) tween(carGraphics, { rotation: 0 }, { duration: 150, easing: tween.easeInOut }); } }); // Smooth X movement (faster lane change) tween(self, { x: self.targetX }, { duration: 400, easing: tween.easeInOut, onFinish: function onFinish() { self.isChangingLanes = false; } }); } } // Update collision cooldown if (self.collisionCooldown > 0) { self.collisionCooldown--; } // Check for collision with other bot cars before moving var canMove = true; var needsLaneChange = false; for (var b = 0; b < botCars.length; b++) { var otherBot = botCars[b]; if (otherBot !== self && otherBot.intersects) { var distanceX = Math.abs(otherBot.x - self.x); var distanceY = Math.abs(otherBot.y - self.y); // Direct collision detection with cooldown if (otherBot.intersects(self) && self.collisionCooldown <= 0) { // Set collision cooldown to prevent frequent collisions self.collisionCooldown = 60; // 1 second cooldown otherBot.collisionCooldown = 60; // Reduce speed less drastically self.currentSpeed = Math.max(4, self.currentSpeed - 2); otherBot.currentSpeed = Math.max(4, otherBot.currentSpeed - 2); // Push cars apart more gently if (self.x < otherBot.x) { self.x -= 30; otherBot.x += 30; } else { self.x += 30; otherBot.x -= 30; } canMove = false; } // Improved collision avoidance - larger detection zone else if (distanceX < 250 && distanceY < 500) { // Bot is getting close, take evasive action if (otherBot.y < self.y && distanceY < 350) { // Bot ahead, slow down and consider lane change self.currentSpeed = Math.max(6, self.currentSpeed - 1); needsLaneChange = true; canMove = false; } else if (otherBot.y > self.y && distanceY < 300) { // Bot behind is catching up, speed up slightly self.currentSpeed = Math.min(self.baseSpeed + 2, self.currentSpeed + 0.5); } } } } // Trigger lane change more aggressively when needed if (needsLaneChange && !self.isChangingLanes && Math.random() < 0.15) { // 15% chance to change lanes when avoiding collision var availableLanes = []; for (var lane = 0; lane < 3; lane++) { if (lane !== self.lanePosition) { availableLanes.push(lane); } } if (availableLanes.length > 0) { var newLane = availableLanes[Math.floor(Math.random() * availableLanes.length)]; self.lanePosition = newLane; self.targetX = 400 + self.lanePosition * 600; self.isChangingLanes = true; // Smooth lane change with tween and rotation var direction = self.targetX > self.x ? 1 : -1; // Add slight rotation during lane change for stability (reduced rotation) tween(carGraphics, { rotation: direction * 0.05 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { // Return to straight after lane change (faster return) tween(carGraphics, { rotation: 0 }, { duration: 150, easing: tween.easeInOut }); } }); // Smooth X movement (faster lane change) tween(self, { x: self.targetX }, { duration: 300, // Faster lane change for collision avoidance easing: tween.easeInOut, onFinish: function onFinish() { self.isChangingLanes = false; } }); } } // Move forward (relative to player) - faster movement if (canMove) { self.y += raceSpeed - self.currentSpeed; } else { self.y += raceSpeed - self.currentSpeed * 0.5; // Move slower when blocked } // Keep within screen bounds if (self.x < 100) { self.x = 100; } if (self.x > 1948) { self.x = 1948; } // Track bot lap progress if (!self.hasFinished) { self.raceDistance += self.currentSpeed; if (self.raceDistance >= lapDistance) { self.raceDistance = 0; self.lapCount++; // Finish after completing 10 laps if (self.lapCount > 10) { self.hasFinished = true; botsFinished++; } } } }; return self; }); var FinishLine = Container.expand(function () { var self = Container.call(this); // Create left pole var leftPole = self.attachAsset('finishPole', { anchorX: 0.5, anchorY: 1 }); leftPole.x = -800; leftPole.y = 0; // Create right pole var rightPole = self.attachAsset('finishPole', { anchorX: 0.5, anchorY: 1 }); rightPole.x = 800; rightPole.y = 0; // Create checkered pattern for (var i = 0; i < 16; i++) { for (var j = 0; j < 2; j++) { var isBlack = (i + j) % 2 === 0; var square = self.attachAsset(isBlack ? 'checkeredSquare1' : 'checkeredSquare2', { anchorX: 0.5, anchorY: 0.5 }); square.x = (i - 7.5) * 100; square.y = (j - 0.5) * 40; } } self.update = function () { self.y += raceSpeed; }; return self; }); var GameMenu = Container.expand(function () { var self = Container.call(this); // Menu background var menuBg = self.attachAsset('menuBackground', { anchorX: 0.5, anchorY: 0.5 }); menuBg.alpha = 0.95; // Menu panel var menuPanel = self.attachAsset('menuPanel', { anchorX: 0.5, anchorY: 0.5 }); menuPanel.y = -100; // Title var titleText = new Text2('RACING CHAMPIONSHIP', { size: 70, fill: 0xFFD700 }); titleText.anchor.set(0.5, 0.5); titleText.y = -600; self.addChild(titleText); // Instructions var instructionText = new Text2('Drag to steer your car\nComplete 10 laps to win!\nAvoid barriers and other cars', { size: 40, fill: 0xFFFFFF, align: 'center' }); instructionText.anchor.set(0.5, 0.5); instructionText.y = -400; self.addChild(instructionText); // Car selection display self.selectedCarIndex = 0; var carPreview = self.attachAsset('playerCar', { anchorX: 0.5, anchorY: 0.5 }); carPreview.y = -150; carPreview.scaleX = 0.8; carPreview.scaleY = 0.8; var carLabel = new Text2('Your Car', { size: 35, fill: 0x00FF00 }); carLabel.anchor.set(0.5, 0.5); carLabel.y = -50; self.addChild(carLabel); // Start button var startButton = new MenuButton('start', 'START RACE', function () { self.startRace(); }); startButton.y = 200; self.addChild(startButton); // Best time display (placeholder) var bestTimeText = new Text2('Best Position: Not Set', { size: 30, fill: 0xCCCCCC }); bestTimeText.anchor.set(0.5, 0.5); bestTimeText.y = 350; self.addChild(bestTimeText); self.startRace = function () { // Hide menu with animation tween(self, { alpha: 0, y: self.y - 200 }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { self.destroy(); // Start the race countdown LK.setTimeout(function () { raceStartSequence = true; }, 500); } }); }; return self; }); var MenuButton = Container.expand(function (buttonType, text, onClick) { var self = Container.call(this); var buttonAsset = buttonType === 'start' ? 'startButton' : 'carSelectButton'; var buttonGraphics = self.attachAsset(buttonAsset, { anchorX: 0.5, anchorY: 0.5 }); var buttonText = new Text2(text, { size: buttonType === 'start' ? 50 : 35, fill: 0xFFFFFF }); buttonText.anchor.set(0.5, 0.5); self.addChild(buttonText); self.onClick = onClick; self.down = function (x, y, obj) { // Button press effect tween(buttonGraphics, { scaleX: 0.95, scaleY: 0.95 }, { duration: 100, easing: tween.easeOut }); if (self.onClick) { self.onClick(); } }; self.up = function (x, y, obj) { // Button release effect tween(buttonGraphics, { scaleX: 1.0, scaleY: 1.0 }, { duration: 100, easing: tween.easeOut }); }; return self; }); var PlayerCar = Container.expand(function () { var self = Container.call(this); var carGraphics = self.attachAsset('playerCar', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 8; self.maxSpeed = 350; self.acceleration = 0.2; self.currentSpeed = 0; self.update = function () { // Simulate acceleration/deceleration based on movement if (Math.abs(self.lastX - self.x) > 5) { self.currentSpeed = Math.min(self.maxSpeed, self.currentSpeed + self.acceleration); } else { self.currentSpeed = Math.max(0, self.currentSpeed - self.acceleration * 0.5); } self.lastX = self.x; }; return self; }); var RoadLine = Container.expand(function () { var self = Container.call(this); var lineGraphics = self.attachAsset('roadLine', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 8; self.update = function () { self.y += raceSpeed; }; return self; }); var TrackEdge = Container.expand(function (barrierIndex) { var self = Container.call(this); // Use different barrier assets based on index (1-5) var barrierAssetName = 'trackBarrier' + (barrierIndex % 5 + 1); var edgeGraphics = self.attachAsset(barrierAssetName, { anchorX: 0.5, anchorY: 0.5 }); self.update = function () { self.y += raceSpeed; }; return self; }); var TrafficLight = Container.expand(function () { var self = Container.call(this); // Create larger frame background (5x bigger to fit 5 lights) var lightBackground = self.attachAsset('trafficLight', { anchorX: 0.5, anchorY: 0.5 }); lightBackground.scaleX = 5; lightBackground.scaleY = 3; // Create 5 lights in a horizontal row self.lights = []; for (var i = 0; i < 5; i++) { var light = self.attachAsset('lightRed', { anchorX: 0.5, anchorY: 0.5 }); light.scaleX = 2; light.scaleY = 2; light.x = (i - 2) * 160; // Space lights 160 pixels apart so they barely touch (80px radius * 2 scale * 2 = 160px diameter) light.alpha = 1.0; light.tint = 0xff0000; // Start all red self.lights.push(light); } self.setAllRed = function () { for (var i = 0; i < self.lights.length; i++) { self.lights[i].tint = 0xff0000; // Red color // Add glow effect with tween tween(self.lights[i], { scaleX: 2.3, scaleY: 2.3 }, { duration: 200, easing: tween.easeOut }); } }; self.setLightGreen = function (lightIndex) { if (lightIndex >= 0 && lightIndex < self.lights.length) { var light = self.lights[lightIndex]; light.tint = 0x00ff00; // Green color light.alpha = 1.0; // Ensure full visibility // Force the light to render on top by moving to front self.removeChild(light); self.addChild(light); // Add glow effect with tween tween(light, { scaleX: 2.3, scaleY: 2.3 }, { duration: 200, easing: tween.easeOut }); } }; self.setOff = function () { for (var i = 0; i < self.lights.length; i++) { self.lights[i].alpha = 0.2; // Reset scale self.lights[i].scaleX = 2; self.lights[i].scaleY = 2; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2d2d2d }); /**** * Game Code ****/ // Game variables var player; var botCars = []; var roadLines = []; var finishLines = []; var trackEdges = []; var botSpawnTimer = 0; var lineSpawnTimer = 0; var checkpointTimer = 0; var raceSpeed = 8; var lapNumber = 1; var position = 9; // Starting position (9th out of 9) var raceDistance = 0; var lapDistance = 5000; // Distance for one lap var raceFinished = false; var finalPosition = 9; var botsFinished = 0; var raceStarted = false; var trafficLights = []; var lightSequenceStep = 0; var lightSequenceTimer = 0; var raceStartSequence = false; var startText = null; var gameMenu = null; var menuActive = true; // Show menu first gameMenu = new GameMenu(); gameMenu.x = 1024; gameMenu.y = 1366; game.addChild(gameMenu); // UI Elements (initially hidden) var positionTxt = new Text2('Position: 9/9', { size: 50, fill: 0xFFFFFF }); positionTxt.anchor.set(0, 0); positionTxt.alpha = 0; // Hide initially LK.gui.topLeft.addChild(positionTxt); positionTxt.x = 120; positionTxt.y = 20; var lapTxt = new Text2('Lap: 1/10', { size: 40, fill: 0xFFFF00 }); lapTxt.anchor.set(1, 0); lapTxt.alpha = 0; // Hide initially LK.gui.topRight.addChild(lapTxt); lapTxt.x = -20; lapTxt.y = 20; var speedTxt = new Text2('Speed: 0', { size: 35, fill: 0x00FF00 }); speedTxt.anchor.set(0, 0); speedTxt.alpha = 0; // Hide initially LK.gui.topLeft.addChild(speedTxt); speedTxt.x = 120; speedTxt.y = 80; // Initialize player player = new PlayerCar(); player.x = 1300; player.y = 2450; // Player starts at the back of the vertical line player.lastX = player.x; game.addChild(player); // Create starting grid with 8 bot cars in vertical single-file line var startingPositions = [ // Position 1 (pole position) { x: 700, y: 500 }, // Position 2 { x: 700, y: 1050 }, // Position 3 { x: 700, y: 1600 }, // Position 4 { x: 700, y: 2150 }, // Position 5 { x: 1300, y: 250 }, // Position 6 { x: 1300, y: 800 }, // Position 7 { x: 1300, y: 1350 }, // Position 8 { x: 1300, y: 1900 } // Last position ]; for (var i = 0; i < 8; i++) { var bot = new BotCar(i); bot.x = startingPositions[i].x; bot.y = startingPositions[i].y; bot.startingPosition = i + 1; botCars.push(bot); game.addChild(bot); } // Create 5-light traffic light system var trafficLight = new TrafficLight(); trafficLight.x = 1024; // Center of screen trafficLight.y = 600; trafficLight.setAllRed(); trafficLights.push(trafficLight); game.addChild(trafficLight); // Create start text (initially hidden) startText = new Text2('START!', { size: 120, fill: 0x00ff00 }); startText.anchor.set(0.5, 0.5); startText.x = 1024; startText.y = 1000; startText.alpha = 0; game.addChild(startText); // Initialize race elements but don't start sequence yet (menu controls this) // Race elements are created but hidden until menu starts the race // Create track barriers - 5 continuous barriers on each side with no gaps var leftBarriers = []; var rightBarriers = []; for (var i = 0; i < 5; i++) { // Left side barriers var leftBarrier = new TrackEdge(i); leftBarrier.x = 150; leftBarrier.y = i * 546 - 200; // Position barriers adjacently based on barrier height (546px) leftBarrier.side = 'left'; leftBarriers.push(leftBarrier); trackEdges.push(leftBarrier); game.addChild(leftBarrier); // Right side barriers var rightBarrier = new TrackEdge(i); rightBarrier.x = 1898; rightBarrier.y = i * 546 - 200; // Position barriers adjacently based on barrier height (546px) rightBarrier.side = 'right'; rightBarriers.push(rightBarrier); trackEdges.push(rightBarrier); game.addChild(rightBarrier); } // Create initial road lines (add to background first) for (var i = 0; i < 15; i++) { var line1 = new RoadLine(); line1.x = 700; line1.y = i * 200 - 100; roadLines.push(line1); game.addChildAt(line1, 0); var line2 = new RoadLine(); line2.x = 1300; line2.y = i * 200 - 100; roadLines.push(line2); game.addChildAt(line2, 0); } // Input handling var dragTarget = null; game.down = function (x, y, obj) { if (!menuActive) { dragTarget = player; handleMove(x, y, obj); } }; game.up = function (x, y, obj) { dragTarget = null; }; function handleMove(x, y, obj) { if (dragTarget && !menuActive) { dragTarget.x = x; // Keep player within racing lanes if (dragTarget.x < 300) { dragTarget.x = 300; } if (dragTarget.x > 1748) { dragTarget.x = 1748; } // Increase speed based on movement if (Math.abs(dragTarget.x - dragTarget.lastX) > 5) { raceSpeed = Math.min(25, raceSpeed + 0.2); } } } game.move = handleMove; // Spawn bot car function function spawnBotCar() { var carIndex = Math.floor(Math.random() * 8); var bot = new BotCar(carIndex); bot.x = 400 + Math.floor(Math.random() * 3) * 600; // Random lane bot.y = -100; botCars.push(bot); game.addChild(bot); } // Spawn road lines function function spawnRoadLines() { var line1 = new RoadLine(); line1.x = 700; line1.y = -50; roadLines.push(line1); game.addChildAt(line1, 0); var line2 = new RoadLine(); line2.x = 1300; line2.y = -50; roadLines.push(line2); game.addChildAt(line2, 0); } // Main game update game.update = function () { // Only update race mechanics if race has started if (raceStarted) { // Update race distance raceDistance += raceSpeed; // Update speed display (scale to show realistic racing speeds) speedTxt.setText('Speed: ' + Math.floor(raceSpeed * 10) + ' km/h'); } else { // Show stationary speed during countdown speedTxt.setText('Speed: 0'); } // Only handle race mechanics if race has started if (raceStarted) { // Check for lap completion or finish line crossing if (raceDistance >= lapDistance && !raceFinished) { raceDistance = 0; lapNumber++; lapTxt.setText('Lap: ' + lapNumber + '/10'); LK.getSound('checkpoint').play(); } } // Check for race completion after lap 10 if (lapNumber >= 10 && !raceFinished) { raceFinished = true; finalPosition = botsFinished + 1; // Player finishes after bots that already finished // Show single checkered flag at finish line var finishFlag = new FinishLine(); finishFlag.x = 1024; // Center of screen finishFlag.y = -200; // Start above screen finishLines.push(finishFlag); game.addChild(finishFlag); // Create result text var resultTxt = new Text2('Race Finished!\nFinal Position: ' + finalPosition + '/9\nLaps Completed: 10', { size: 60, fill: 0xFFFFFF, align: 'center' }); resultTxt.anchor.set(0.5, 0.5); resultTxt.x = 1024; resultTxt.y = 1366; game.addChild(resultTxt); LK.setScore(900 - finalPosition * 100); // Higher score for better position // Show you win after 3 seconds LK.setTimeout(function () { LK.showYouWin(); }, 3000); } // Handle traffic light sequence if (raceStartSequence && !raceStarted) { // Show UI elements when race sequence starts if (lightSequenceTimer === 0) { tween(positionTxt, { alpha: 1 }, { duration: 500 }); tween(lapTxt, { alpha: 1 }, { duration: 500 }); tween(speedTxt, { alpha: 1 }, { duration: 500 }); menuActive = false; } lightSequenceTimer++; // Turn lights green one by one every 60 frames (1 second each) if (lightSequenceTimer === 60) { // First light turns green after 1 second trafficLights[0].setLightGreen(0); } else if (lightSequenceTimer === 120) { // Second light turns green after 2 seconds trafficLights[0].setLightGreen(1); } else if (lightSequenceTimer === 180) { // Third light turns green after 3 seconds trafficLights[0].setLightGreen(2); } else if (lightSequenceTimer === 240) { // Fourth light turns green after 4 seconds trafficLights[0].setLightGreen(3); } else if (lightSequenceTimer === 300) { // Fifth light turns green after 5 seconds - RACE STARTS trafficLights[0].setLightGreen(4); // Show START text with tween animation tween(startText, { alpha: 1, scaleX: 1.5, scaleY: 1.5 }, { duration: 500, easing: tween.bounceOut, onFinish: function onFinish() { // Fade out start text after 1 second tween(startText, { alpha: 0 }, { duration: 1000 }); } }); raceStarted = true; // Activate bot cars with adjusted speeds for (var i = 0; i < botCars.length; i++) { botCars[i].currentSpeed = botCars[i].baseSpeed; // Add very slight speed variation to maintain distinct speeds botCars[i].currentSpeed += Math.random() * 0.5 - 0.25; // +/- 0.25 speed variation (very small range) } // Hide traffic light with animation tween(trafficLights[0], { alpha: 0, y: trafficLights[0].y - 100 }, { duration: 1000, easing: tween.easeOut }); } } // Spawn road lines lineSpawnTimer++; if (lineSpawnTimer >= 25) { lineSpawnTimer = 0; spawnRoadLines(); } // Update and check bot cars for (var i = botCars.length - 1; i >= 0; i--) { var bot = botCars[i]; if (bot.lastY === undefined) { bot.lastY = bot.y; } if (bot.lastIntersecting === undefined) { bot.lastIntersecting = false; } // Gradual speed recovery to maintain bot speed differences if (raceStarted && bot.currentSpeed < bot.baseSpeed) { bot.currentSpeed = Math.min(bot.baseSpeed, bot.currentSpeed + 0.1); } // Remove bots that go off screen if (bot.lastY <= 2800 && bot.y > 2800) { bot.destroy(); botCars.splice(i, 1); continue; } // Check collision with player var currentIntersecting = bot.intersects(player); if (!bot.lastIntersecting && currentIntersecting) { LK.getSound('collision').play(); // Slow down both cars on collision raceSpeed = Math.max(3, raceSpeed - 2); // Move player away from collision if (player.x < bot.x) { player.x -= 30; } else { player.x += 30; } } bot.lastY = bot.y; bot.lastIntersecting = currentIntersecting; } // Update road lines for (var i = roadLines.length - 1; i >= 0; i--) { var line = roadLines[i]; if (line.lastY === undefined) { line.lastY = line.y; } // Remove lines that go off screen if (line.lastY <= 2800 && line.y > 2800) { line.destroy(); roadLines.splice(i, 1); continue; } line.lastY = line.y; } // Update track edges and check collisions for (var i = trackEdges.length - 1; i >= 0; i--) { var edge = trackEdges[i]; if (edge.lastY === undefined) { edge.lastY = edge.y; } // Check collision with player if (edge.intersects && edge.intersects(player)) { // Play collision sound LK.getSound('collision').play(); // Reduce speed significantly raceSpeed = Math.max(2, raceSpeed - 4); // Bounce player away from barrier if (edge.side === 'left') { // Hit left barrier, push player right tween(player, { x: player.x + 100 }, { duration: 300, easing: tween.easeOut }); } else if (edge.side === 'right') { // Hit right barrier, push player left tween(player, { x: player.x - 100 }, { duration: 300, easing: tween.easeOut }); } // Flash the barrier red to show collision tween(edge, { tint: 0xff0000 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(edge, { tint: 0xffffff }, { duration: 300, easing: tween.easeIn }); } }); } // Check collisions with bot cars for (var j = 0; j < botCars.length; j++) { var bot = botCars[j]; if (edge.intersects && edge.intersects(bot)) { // Reduce bot speed bot.currentSpeed = Math.max(2, bot.currentSpeed - 3); // Bounce bot away from barrier if (edge.side === 'left') { // Hit left barrier, push bot right tween(bot, { x: bot.x + 80 }, { duration: 250, easing: tween.easeOut }); } else if (edge.side === 'right') { // Hit right barrier, push bot left tween(bot, { x: bot.x - 80 }, { duration: 250, easing: tween.easeOut }); } } } // Remove edges that go off screen and spawn new ones if (edge.lastY <= 2800 && edge.y > 2800) { edge.destroy(); trackEdges.splice(i, 1); continue; } edge.lastY = edge.y; } // Update finish lines for (var i = finishLines.length - 1; i >= 0; i--) { var finishLine = finishLines[i]; if (finishLine.lastY === undefined) { finishLine.lastY = finishLine.y; } // Remove finish lines that go off screen if (finishLine.lastY <= 2800 && finishLine.y > 2800) { finishLine.destroy(); finishLines.splice(i, 1); continue; } finishLine.lastY = finishLine.y; } // Spawn new track barriers - maintain continuous coverage with no gaps if (LK.ticks % 40 === 0) { // Spawn more frequently to ensure continuous coverage var barrierIndex = Math.floor(Math.random() * 5); // Find the topmost barrier position for each side to place new ones adjacent var leftTopY = -400; var rightTopY = -400; // Find the highest (lowest Y value) barrier on each side for (var k = 0; k < leftBarriers.length; k++) { if (leftBarriers[k].y < leftTopY) { leftTopY = leftBarriers[k].y; } } for (var k = 0; k < rightBarriers.length; k++) { if (rightBarriers[k].y < rightTopY) { rightTopY = rightBarriers[k].y; } } var leftBarrier = new TrackEdge(barrierIndex); leftBarrier.x = 150; // Position new barrier to be adjacent to the topmost barrier (accounting for barrier height) leftBarrier.y = leftTopY - 546; // Use barrier height to ensure no gap leftBarrier.side = 'left'; leftBarriers.push(leftBarrier); trackEdges.push(leftBarrier); game.addChild(leftBarrier); var rightBarrier = new TrackEdge(barrierIndex); rightBarrier.x = 1898; // Position new barrier to be adjacent to the topmost barrier (accounting for barrier height) rightBarrier.y = rightTopY - 546; // Use barrier height to ensure no gap rightBarrier.side = 'right'; rightBarriers.push(rightBarrier); trackEdges.push(rightBarrier); game.addChild(rightBarrier); // Remove barriers from arrays when they go off screen for (var k = leftBarriers.length - 1; k >= 0; k--) { if (leftBarriers[k].y > 2800) { leftBarriers.splice(k, 1); } } for (var k = rightBarriers.length - 1; k >= 0; k--) { if (rightBarriers[k].y > 2800) { rightBarriers.splice(k, 1); } } } // Calculate position based on bot cars behind player and finished bots if (!raceFinished && raceStarted) { var carsAhead = botsFinished; // Count finished bots as ahead for (var i = 0; i < botCars.length; i++) { var bot = botCars[i]; if (!bot.hasFinished) { // Compare lap progress for unfinished bots if (bot.lapCount > lapNumber || bot.lapCount === lapNumber && bot.raceDistance > raceDistance) { carsAhead++; } } } position = Math.max(1, Math.min(9, carsAhead + 1)); // Clamp position between 1-9 positionTxt.setText('Position: ' + position + '/9'); } else if (raceFinished) { positionTxt.setText('Position: ' + finalPosition + '/9 - FINISHED'); } else { // Before race starts, show starting position positionTxt.setText('Position: 9/9'); } // Gradually increase race speed if (LK.ticks % 300 === 0) { // Every 5 seconds raceSpeed = Math.min(12, raceSpeed + 0.1); } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var BotCar = Container.expand(function (carIndex) {
var self = Container.call(this);
// Use different car assets based on car index
var carAssetName = 'botCar' + (carIndex % 8 + 1);
var carGraphics = self.attachAsset(carAssetName, {
anchorX: 0.5,
anchorY: 0.5
});
// Create more varied bot speeds based on car index for distinct racing styles
var speedVariations = [14,
// Fast aggressive bot
11,
// Medium-fast bot
16,
// Very fast bot
9,
// Slower cautious bot
13,
// Medium bot
18,
// Fastest bot
8,
// Slowest bot
15 // Fast bot
];
self.baseSpeed = speedVariations[carIndex % 8]; // Assign specific speed based on car index
self.currentSpeed = 0; // Start stationary until race begins
self.lanePosition = Math.floor(Math.random() * 3); // 0=left, 1=center, 2=right
self.targetX = 400 + self.lanePosition * 600; // Lane positions
self.aiTimer = 0;
self.carNumber = carIndex + 1;
self.isChangingLanes = false; // Track lane changing state
self.collisionCooldown = 0; // Prevent frequent collisions
// Bot cars now use their default asset colors without tinting
self.lapCount = 1;
self.raceDistance = 0;
self.hasFinished = false;
self.update = function () {
// Only move if race has started
if (!raceStarted) {
return;
}
// AI behavior - change lanes occasionally
self.aiTimer++;
if (self.aiTimer > 120 && Math.random() < 0.03 && !self.isChangingLanes) {
// Every 2 seconds, 3% chance, only if not already changing lanes
self.aiTimer = 0;
var newLane = Math.floor(Math.random() * 3);
if (newLane !== self.lanePosition) {
self.lanePosition = newLane;
self.targetX = 400 + self.lanePosition * 600;
self.isChangingLanes = true;
// Smooth lane change with tween and rotation
var direction = self.targetX > self.x ? 1 : -1;
// Add slight rotation during lane change for stability (reduced rotation)
tween(carGraphics, {
rotation: direction * 0.05
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
// Return to straight after lane change (faster return)
tween(carGraphics, {
rotation: 0
}, {
duration: 150,
easing: tween.easeInOut
});
}
});
// Smooth X movement (faster lane change)
tween(self, {
x: self.targetX
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
self.isChangingLanes = false;
}
});
}
}
// Update collision cooldown
if (self.collisionCooldown > 0) {
self.collisionCooldown--;
}
// Check for collision with other bot cars before moving
var canMove = true;
var needsLaneChange = false;
for (var b = 0; b < botCars.length; b++) {
var otherBot = botCars[b];
if (otherBot !== self && otherBot.intersects) {
var distanceX = Math.abs(otherBot.x - self.x);
var distanceY = Math.abs(otherBot.y - self.y);
// Direct collision detection with cooldown
if (otherBot.intersects(self) && self.collisionCooldown <= 0) {
// Set collision cooldown to prevent frequent collisions
self.collisionCooldown = 60; // 1 second cooldown
otherBot.collisionCooldown = 60;
// Reduce speed less drastically
self.currentSpeed = Math.max(4, self.currentSpeed - 2);
otherBot.currentSpeed = Math.max(4, otherBot.currentSpeed - 2);
// Push cars apart more gently
if (self.x < otherBot.x) {
self.x -= 30;
otherBot.x += 30;
} else {
self.x += 30;
otherBot.x -= 30;
}
canMove = false;
}
// Improved collision avoidance - larger detection zone
else if (distanceX < 250 && distanceY < 500) {
// Bot is getting close, take evasive action
if (otherBot.y < self.y && distanceY < 350) {
// Bot ahead, slow down and consider lane change
self.currentSpeed = Math.max(6, self.currentSpeed - 1);
needsLaneChange = true;
canMove = false;
} else if (otherBot.y > self.y && distanceY < 300) {
// Bot behind is catching up, speed up slightly
self.currentSpeed = Math.min(self.baseSpeed + 2, self.currentSpeed + 0.5);
}
}
}
}
// Trigger lane change more aggressively when needed
if (needsLaneChange && !self.isChangingLanes && Math.random() < 0.15) {
// 15% chance to change lanes when avoiding collision
var availableLanes = [];
for (var lane = 0; lane < 3; lane++) {
if (lane !== self.lanePosition) {
availableLanes.push(lane);
}
}
if (availableLanes.length > 0) {
var newLane = availableLanes[Math.floor(Math.random() * availableLanes.length)];
self.lanePosition = newLane;
self.targetX = 400 + self.lanePosition * 600;
self.isChangingLanes = true;
// Smooth lane change with tween and rotation
var direction = self.targetX > self.x ? 1 : -1;
// Add slight rotation during lane change for stability (reduced rotation)
tween(carGraphics, {
rotation: direction * 0.05
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
// Return to straight after lane change (faster return)
tween(carGraphics, {
rotation: 0
}, {
duration: 150,
easing: tween.easeInOut
});
}
});
// Smooth X movement (faster lane change)
tween(self, {
x: self.targetX
}, {
duration: 300,
// Faster lane change for collision avoidance
easing: tween.easeInOut,
onFinish: function onFinish() {
self.isChangingLanes = false;
}
});
}
}
// Move forward (relative to player) - faster movement
if (canMove) {
self.y += raceSpeed - self.currentSpeed;
} else {
self.y += raceSpeed - self.currentSpeed * 0.5; // Move slower when blocked
}
// Keep within screen bounds
if (self.x < 100) {
self.x = 100;
}
if (self.x > 1948) {
self.x = 1948;
}
// Track bot lap progress
if (!self.hasFinished) {
self.raceDistance += self.currentSpeed;
if (self.raceDistance >= lapDistance) {
self.raceDistance = 0;
self.lapCount++;
// Finish after completing 10 laps
if (self.lapCount > 10) {
self.hasFinished = true;
botsFinished++;
}
}
}
};
return self;
});
var FinishLine = Container.expand(function () {
var self = Container.call(this);
// Create left pole
var leftPole = self.attachAsset('finishPole', {
anchorX: 0.5,
anchorY: 1
});
leftPole.x = -800;
leftPole.y = 0;
// Create right pole
var rightPole = self.attachAsset('finishPole', {
anchorX: 0.5,
anchorY: 1
});
rightPole.x = 800;
rightPole.y = 0;
// Create checkered pattern
for (var i = 0; i < 16; i++) {
for (var j = 0; j < 2; j++) {
var isBlack = (i + j) % 2 === 0;
var square = self.attachAsset(isBlack ? 'checkeredSquare1' : 'checkeredSquare2', {
anchorX: 0.5,
anchorY: 0.5
});
square.x = (i - 7.5) * 100;
square.y = (j - 0.5) * 40;
}
}
self.update = function () {
self.y += raceSpeed;
};
return self;
});
var GameMenu = Container.expand(function () {
var self = Container.call(this);
// Menu background
var menuBg = self.attachAsset('menuBackground', {
anchorX: 0.5,
anchorY: 0.5
});
menuBg.alpha = 0.95;
// Menu panel
var menuPanel = self.attachAsset('menuPanel', {
anchorX: 0.5,
anchorY: 0.5
});
menuPanel.y = -100;
// Title
var titleText = new Text2('RACING CHAMPIONSHIP', {
size: 70,
fill: 0xFFD700
});
titleText.anchor.set(0.5, 0.5);
titleText.y = -600;
self.addChild(titleText);
// Instructions
var instructionText = new Text2('Drag to steer your car\nComplete 10 laps to win!\nAvoid barriers and other cars', {
size: 40,
fill: 0xFFFFFF,
align: 'center'
});
instructionText.anchor.set(0.5, 0.5);
instructionText.y = -400;
self.addChild(instructionText);
// Car selection display
self.selectedCarIndex = 0;
var carPreview = self.attachAsset('playerCar', {
anchorX: 0.5,
anchorY: 0.5
});
carPreview.y = -150;
carPreview.scaleX = 0.8;
carPreview.scaleY = 0.8;
var carLabel = new Text2('Your Car', {
size: 35,
fill: 0x00FF00
});
carLabel.anchor.set(0.5, 0.5);
carLabel.y = -50;
self.addChild(carLabel);
// Start button
var startButton = new MenuButton('start', 'START RACE', function () {
self.startRace();
});
startButton.y = 200;
self.addChild(startButton);
// Best time display (placeholder)
var bestTimeText = new Text2('Best Position: Not Set', {
size: 30,
fill: 0xCCCCCC
});
bestTimeText.anchor.set(0.5, 0.5);
bestTimeText.y = 350;
self.addChild(bestTimeText);
self.startRace = function () {
// Hide menu with animation
tween(self, {
alpha: 0,
y: self.y - 200
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
self.destroy();
// Start the race countdown
LK.setTimeout(function () {
raceStartSequence = true;
}, 500);
}
});
};
return self;
});
var MenuButton = Container.expand(function (buttonType, text, onClick) {
var self = Container.call(this);
var buttonAsset = buttonType === 'start' ? 'startButton' : 'carSelectButton';
var buttonGraphics = self.attachAsset(buttonAsset, {
anchorX: 0.5,
anchorY: 0.5
});
var buttonText = new Text2(text, {
size: buttonType === 'start' ? 50 : 35,
fill: 0xFFFFFF
});
buttonText.anchor.set(0.5, 0.5);
self.addChild(buttonText);
self.onClick = onClick;
self.down = function (x, y, obj) {
// Button press effect
tween(buttonGraphics, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100,
easing: tween.easeOut
});
if (self.onClick) {
self.onClick();
}
};
self.up = function (x, y, obj) {
// Button release effect
tween(buttonGraphics, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut
});
};
return self;
});
var PlayerCar = Container.expand(function () {
var self = Container.call(this);
var carGraphics = self.attachAsset('playerCar', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.maxSpeed = 350;
self.acceleration = 0.2;
self.currentSpeed = 0;
self.update = function () {
// Simulate acceleration/deceleration based on movement
if (Math.abs(self.lastX - self.x) > 5) {
self.currentSpeed = Math.min(self.maxSpeed, self.currentSpeed + self.acceleration);
} else {
self.currentSpeed = Math.max(0, self.currentSpeed - self.acceleration * 0.5);
}
self.lastX = self.x;
};
return self;
});
var RoadLine = Container.expand(function () {
var self = Container.call(this);
var lineGraphics = self.attachAsset('roadLine', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.update = function () {
self.y += raceSpeed;
};
return self;
});
var TrackEdge = Container.expand(function (barrierIndex) {
var self = Container.call(this);
// Use different barrier assets based on index (1-5)
var barrierAssetName = 'trackBarrier' + (barrierIndex % 5 + 1);
var edgeGraphics = self.attachAsset(barrierAssetName, {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
self.y += raceSpeed;
};
return self;
});
var TrafficLight = Container.expand(function () {
var self = Container.call(this);
// Create larger frame background (5x bigger to fit 5 lights)
var lightBackground = self.attachAsset('trafficLight', {
anchorX: 0.5,
anchorY: 0.5
});
lightBackground.scaleX = 5;
lightBackground.scaleY = 3;
// Create 5 lights in a horizontal row
self.lights = [];
for (var i = 0; i < 5; i++) {
var light = self.attachAsset('lightRed', {
anchorX: 0.5,
anchorY: 0.5
});
light.scaleX = 2;
light.scaleY = 2;
light.x = (i - 2) * 160; // Space lights 160 pixels apart so they barely touch (80px radius * 2 scale * 2 = 160px diameter)
light.alpha = 1.0;
light.tint = 0xff0000; // Start all red
self.lights.push(light);
}
self.setAllRed = function () {
for (var i = 0; i < self.lights.length; i++) {
self.lights[i].tint = 0xff0000; // Red color
// Add glow effect with tween
tween(self.lights[i], {
scaleX: 2.3,
scaleY: 2.3
}, {
duration: 200,
easing: tween.easeOut
});
}
};
self.setLightGreen = function (lightIndex) {
if (lightIndex >= 0 && lightIndex < self.lights.length) {
var light = self.lights[lightIndex];
light.tint = 0x00ff00; // Green color
light.alpha = 1.0; // Ensure full visibility
// Force the light to render on top by moving to front
self.removeChild(light);
self.addChild(light);
// Add glow effect with tween
tween(light, {
scaleX: 2.3,
scaleY: 2.3
}, {
duration: 200,
easing: tween.easeOut
});
}
};
self.setOff = function () {
for (var i = 0; i < self.lights.length; i++) {
self.lights[i].alpha = 0.2;
// Reset scale
self.lights[i].scaleX = 2;
self.lights[i].scaleY = 2;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2d2d2d
});
/****
* Game Code
****/
// Game variables
var player;
var botCars = [];
var roadLines = [];
var finishLines = [];
var trackEdges = [];
var botSpawnTimer = 0;
var lineSpawnTimer = 0;
var checkpointTimer = 0;
var raceSpeed = 8;
var lapNumber = 1;
var position = 9; // Starting position (9th out of 9)
var raceDistance = 0;
var lapDistance = 5000; // Distance for one lap
var raceFinished = false;
var finalPosition = 9;
var botsFinished = 0;
var raceStarted = false;
var trafficLights = [];
var lightSequenceStep = 0;
var lightSequenceTimer = 0;
var raceStartSequence = false;
var startText = null;
var gameMenu = null;
var menuActive = true;
// Show menu first
gameMenu = new GameMenu();
gameMenu.x = 1024;
gameMenu.y = 1366;
game.addChild(gameMenu);
// UI Elements (initially hidden)
var positionTxt = new Text2('Position: 9/9', {
size: 50,
fill: 0xFFFFFF
});
positionTxt.anchor.set(0, 0);
positionTxt.alpha = 0; // Hide initially
LK.gui.topLeft.addChild(positionTxt);
positionTxt.x = 120;
positionTxt.y = 20;
var lapTxt = new Text2('Lap: 1/10', {
size: 40,
fill: 0xFFFF00
});
lapTxt.anchor.set(1, 0);
lapTxt.alpha = 0; // Hide initially
LK.gui.topRight.addChild(lapTxt);
lapTxt.x = -20;
lapTxt.y = 20;
var speedTxt = new Text2('Speed: 0', {
size: 35,
fill: 0x00FF00
});
speedTxt.anchor.set(0, 0);
speedTxt.alpha = 0; // Hide initially
LK.gui.topLeft.addChild(speedTxt);
speedTxt.x = 120;
speedTxt.y = 80;
// Initialize player
player = new PlayerCar();
player.x = 1300;
player.y = 2450; // Player starts at the back of the vertical line
player.lastX = player.x;
game.addChild(player);
// Create starting grid with 8 bot cars in vertical single-file line
var startingPositions = [
// Position 1 (pole position)
{
x: 700,
y: 500
},
// Position 2
{
x: 700,
y: 1050
},
// Position 3
{
x: 700,
y: 1600
},
// Position 4
{
x: 700,
y: 2150
},
// Position 5
{
x: 1300,
y: 250
},
// Position 6
{
x: 1300,
y: 800
},
// Position 7
{
x: 1300,
y: 1350
},
// Position 8
{
x: 1300,
y: 1900
} // Last position
];
for (var i = 0; i < 8; i++) {
var bot = new BotCar(i);
bot.x = startingPositions[i].x;
bot.y = startingPositions[i].y;
bot.startingPosition = i + 1;
botCars.push(bot);
game.addChild(bot);
}
// Create 5-light traffic light system
var trafficLight = new TrafficLight();
trafficLight.x = 1024; // Center of screen
trafficLight.y = 600;
trafficLight.setAllRed();
trafficLights.push(trafficLight);
game.addChild(trafficLight);
// Create start text (initially hidden)
startText = new Text2('START!', {
size: 120,
fill: 0x00ff00
});
startText.anchor.set(0.5, 0.5);
startText.x = 1024;
startText.y = 1000;
startText.alpha = 0;
game.addChild(startText);
// Initialize race elements but don't start sequence yet (menu controls this)
// Race elements are created but hidden until menu starts the race
// Create track barriers - 5 continuous barriers on each side with no gaps
var leftBarriers = [];
var rightBarriers = [];
for (var i = 0; i < 5; i++) {
// Left side barriers
var leftBarrier = new TrackEdge(i);
leftBarrier.x = 150;
leftBarrier.y = i * 546 - 200; // Position barriers adjacently based on barrier height (546px)
leftBarrier.side = 'left';
leftBarriers.push(leftBarrier);
trackEdges.push(leftBarrier);
game.addChild(leftBarrier);
// Right side barriers
var rightBarrier = new TrackEdge(i);
rightBarrier.x = 1898;
rightBarrier.y = i * 546 - 200; // Position barriers adjacently based on barrier height (546px)
rightBarrier.side = 'right';
rightBarriers.push(rightBarrier);
trackEdges.push(rightBarrier);
game.addChild(rightBarrier);
}
// Create initial road lines (add to background first)
for (var i = 0; i < 15; i++) {
var line1 = new RoadLine();
line1.x = 700;
line1.y = i * 200 - 100;
roadLines.push(line1);
game.addChildAt(line1, 0);
var line2 = new RoadLine();
line2.x = 1300;
line2.y = i * 200 - 100;
roadLines.push(line2);
game.addChildAt(line2, 0);
}
// Input handling
var dragTarget = null;
game.down = function (x, y, obj) {
if (!menuActive) {
dragTarget = player;
handleMove(x, y, obj);
}
};
game.up = function (x, y, obj) {
dragTarget = null;
};
function handleMove(x, y, obj) {
if (dragTarget && !menuActive) {
dragTarget.x = x;
// Keep player within racing lanes
if (dragTarget.x < 300) {
dragTarget.x = 300;
}
if (dragTarget.x > 1748) {
dragTarget.x = 1748;
}
// Increase speed based on movement
if (Math.abs(dragTarget.x - dragTarget.lastX) > 5) {
raceSpeed = Math.min(25, raceSpeed + 0.2);
}
}
}
game.move = handleMove;
// Spawn bot car function
function spawnBotCar() {
var carIndex = Math.floor(Math.random() * 8);
var bot = new BotCar(carIndex);
bot.x = 400 + Math.floor(Math.random() * 3) * 600; // Random lane
bot.y = -100;
botCars.push(bot);
game.addChild(bot);
}
// Spawn road lines function
function spawnRoadLines() {
var line1 = new RoadLine();
line1.x = 700;
line1.y = -50;
roadLines.push(line1);
game.addChildAt(line1, 0);
var line2 = new RoadLine();
line2.x = 1300;
line2.y = -50;
roadLines.push(line2);
game.addChildAt(line2, 0);
}
// Main game update
game.update = function () {
// Only update race mechanics if race has started
if (raceStarted) {
// Update race distance
raceDistance += raceSpeed;
// Update speed display (scale to show realistic racing speeds)
speedTxt.setText('Speed: ' + Math.floor(raceSpeed * 10) + ' km/h');
} else {
// Show stationary speed during countdown
speedTxt.setText('Speed: 0');
}
// Only handle race mechanics if race has started
if (raceStarted) {
// Check for lap completion or finish line crossing
if (raceDistance >= lapDistance && !raceFinished) {
raceDistance = 0;
lapNumber++;
lapTxt.setText('Lap: ' + lapNumber + '/10');
LK.getSound('checkpoint').play();
}
}
// Check for race completion after lap 10
if (lapNumber >= 10 && !raceFinished) {
raceFinished = true;
finalPosition = botsFinished + 1; // Player finishes after bots that already finished
// Show single checkered flag at finish line
var finishFlag = new FinishLine();
finishFlag.x = 1024; // Center of screen
finishFlag.y = -200; // Start above screen
finishLines.push(finishFlag);
game.addChild(finishFlag);
// Create result text
var resultTxt = new Text2('Race Finished!\nFinal Position: ' + finalPosition + '/9\nLaps Completed: 10', {
size: 60,
fill: 0xFFFFFF,
align: 'center'
});
resultTxt.anchor.set(0.5, 0.5);
resultTxt.x = 1024;
resultTxt.y = 1366;
game.addChild(resultTxt);
LK.setScore(900 - finalPosition * 100); // Higher score for better position
// Show you win after 3 seconds
LK.setTimeout(function () {
LK.showYouWin();
}, 3000);
}
// Handle traffic light sequence
if (raceStartSequence && !raceStarted) {
// Show UI elements when race sequence starts
if (lightSequenceTimer === 0) {
tween(positionTxt, {
alpha: 1
}, {
duration: 500
});
tween(lapTxt, {
alpha: 1
}, {
duration: 500
});
tween(speedTxt, {
alpha: 1
}, {
duration: 500
});
menuActive = false;
}
lightSequenceTimer++;
// Turn lights green one by one every 60 frames (1 second each)
if (lightSequenceTimer === 60) {
// First light turns green after 1 second
trafficLights[0].setLightGreen(0);
} else if (lightSequenceTimer === 120) {
// Second light turns green after 2 seconds
trafficLights[0].setLightGreen(1);
} else if (lightSequenceTimer === 180) {
// Third light turns green after 3 seconds
trafficLights[0].setLightGreen(2);
} else if (lightSequenceTimer === 240) {
// Fourth light turns green after 4 seconds
trafficLights[0].setLightGreen(3);
} else if (lightSequenceTimer === 300) {
// Fifth light turns green after 5 seconds - RACE STARTS
trafficLights[0].setLightGreen(4);
// Show START text with tween animation
tween(startText, {
alpha: 1,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 500,
easing: tween.bounceOut,
onFinish: function onFinish() {
// Fade out start text after 1 second
tween(startText, {
alpha: 0
}, {
duration: 1000
});
}
});
raceStarted = true;
// Activate bot cars with adjusted speeds
for (var i = 0; i < botCars.length; i++) {
botCars[i].currentSpeed = botCars[i].baseSpeed;
// Add very slight speed variation to maintain distinct speeds
botCars[i].currentSpeed += Math.random() * 0.5 - 0.25; // +/- 0.25 speed variation (very small range)
}
// Hide traffic light with animation
tween(trafficLights[0], {
alpha: 0,
y: trafficLights[0].y - 100
}, {
duration: 1000,
easing: tween.easeOut
});
}
}
// Spawn road lines
lineSpawnTimer++;
if (lineSpawnTimer >= 25) {
lineSpawnTimer = 0;
spawnRoadLines();
}
// Update and check bot cars
for (var i = botCars.length - 1; i >= 0; i--) {
var bot = botCars[i];
if (bot.lastY === undefined) {
bot.lastY = bot.y;
}
if (bot.lastIntersecting === undefined) {
bot.lastIntersecting = false;
}
// Gradual speed recovery to maintain bot speed differences
if (raceStarted && bot.currentSpeed < bot.baseSpeed) {
bot.currentSpeed = Math.min(bot.baseSpeed, bot.currentSpeed + 0.1);
}
// Remove bots that go off screen
if (bot.lastY <= 2800 && bot.y > 2800) {
bot.destroy();
botCars.splice(i, 1);
continue;
}
// Check collision with player
var currentIntersecting = bot.intersects(player);
if (!bot.lastIntersecting && currentIntersecting) {
LK.getSound('collision').play();
// Slow down both cars on collision
raceSpeed = Math.max(3, raceSpeed - 2);
// Move player away from collision
if (player.x < bot.x) {
player.x -= 30;
} else {
player.x += 30;
}
}
bot.lastY = bot.y;
bot.lastIntersecting = currentIntersecting;
}
// Update road lines
for (var i = roadLines.length - 1; i >= 0; i--) {
var line = roadLines[i];
if (line.lastY === undefined) {
line.lastY = line.y;
}
// Remove lines that go off screen
if (line.lastY <= 2800 && line.y > 2800) {
line.destroy();
roadLines.splice(i, 1);
continue;
}
line.lastY = line.y;
}
// Update track edges and check collisions
for (var i = trackEdges.length - 1; i >= 0; i--) {
var edge = trackEdges[i];
if (edge.lastY === undefined) {
edge.lastY = edge.y;
}
// Check collision with player
if (edge.intersects && edge.intersects(player)) {
// Play collision sound
LK.getSound('collision').play();
// Reduce speed significantly
raceSpeed = Math.max(2, raceSpeed - 4);
// Bounce player away from barrier
if (edge.side === 'left') {
// Hit left barrier, push player right
tween(player, {
x: player.x + 100
}, {
duration: 300,
easing: tween.easeOut
});
} else if (edge.side === 'right') {
// Hit right barrier, push player left
tween(player, {
x: player.x - 100
}, {
duration: 300,
easing: tween.easeOut
});
}
// Flash the barrier red to show collision
tween(edge, {
tint: 0xff0000
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(edge, {
tint: 0xffffff
}, {
duration: 300,
easing: tween.easeIn
});
}
});
}
// Check collisions with bot cars
for (var j = 0; j < botCars.length; j++) {
var bot = botCars[j];
if (edge.intersects && edge.intersects(bot)) {
// Reduce bot speed
bot.currentSpeed = Math.max(2, bot.currentSpeed - 3);
// Bounce bot away from barrier
if (edge.side === 'left') {
// Hit left barrier, push bot right
tween(bot, {
x: bot.x + 80
}, {
duration: 250,
easing: tween.easeOut
});
} else if (edge.side === 'right') {
// Hit right barrier, push bot left
tween(bot, {
x: bot.x - 80
}, {
duration: 250,
easing: tween.easeOut
});
}
}
}
// Remove edges that go off screen and spawn new ones
if (edge.lastY <= 2800 && edge.y > 2800) {
edge.destroy();
trackEdges.splice(i, 1);
continue;
}
edge.lastY = edge.y;
}
// Update finish lines
for (var i = finishLines.length - 1; i >= 0; i--) {
var finishLine = finishLines[i];
if (finishLine.lastY === undefined) {
finishLine.lastY = finishLine.y;
}
// Remove finish lines that go off screen
if (finishLine.lastY <= 2800 && finishLine.y > 2800) {
finishLine.destroy();
finishLines.splice(i, 1);
continue;
}
finishLine.lastY = finishLine.y;
}
// Spawn new track barriers - maintain continuous coverage with no gaps
if (LK.ticks % 40 === 0) {
// Spawn more frequently to ensure continuous coverage
var barrierIndex = Math.floor(Math.random() * 5);
// Find the topmost barrier position for each side to place new ones adjacent
var leftTopY = -400;
var rightTopY = -400;
// Find the highest (lowest Y value) barrier on each side
for (var k = 0; k < leftBarriers.length; k++) {
if (leftBarriers[k].y < leftTopY) {
leftTopY = leftBarriers[k].y;
}
}
for (var k = 0; k < rightBarriers.length; k++) {
if (rightBarriers[k].y < rightTopY) {
rightTopY = rightBarriers[k].y;
}
}
var leftBarrier = new TrackEdge(barrierIndex);
leftBarrier.x = 150;
// Position new barrier to be adjacent to the topmost barrier (accounting for barrier height)
leftBarrier.y = leftTopY - 546; // Use barrier height to ensure no gap
leftBarrier.side = 'left';
leftBarriers.push(leftBarrier);
trackEdges.push(leftBarrier);
game.addChild(leftBarrier);
var rightBarrier = new TrackEdge(barrierIndex);
rightBarrier.x = 1898;
// Position new barrier to be adjacent to the topmost barrier (accounting for barrier height)
rightBarrier.y = rightTopY - 546; // Use barrier height to ensure no gap
rightBarrier.side = 'right';
rightBarriers.push(rightBarrier);
trackEdges.push(rightBarrier);
game.addChild(rightBarrier);
// Remove barriers from arrays when they go off screen
for (var k = leftBarriers.length - 1; k >= 0; k--) {
if (leftBarriers[k].y > 2800) {
leftBarriers.splice(k, 1);
}
}
for (var k = rightBarriers.length - 1; k >= 0; k--) {
if (rightBarriers[k].y > 2800) {
rightBarriers.splice(k, 1);
}
}
}
// Calculate position based on bot cars behind player and finished bots
if (!raceFinished && raceStarted) {
var carsAhead = botsFinished; // Count finished bots as ahead
for (var i = 0; i < botCars.length; i++) {
var bot = botCars[i];
if (!bot.hasFinished) {
// Compare lap progress for unfinished bots
if (bot.lapCount > lapNumber || bot.lapCount === lapNumber && bot.raceDistance > raceDistance) {
carsAhead++;
}
}
}
position = Math.max(1, Math.min(9, carsAhead + 1)); // Clamp position between 1-9
positionTxt.setText('Position: ' + position + '/9');
} else if (raceFinished) {
positionTxt.setText('Position: ' + finalPosition + '/9 - FINISHED');
} else {
// Before race starts, show starting position
positionTxt.setText('Position: 9/9');
}
// Gradually increase race speed
if (LK.ticks % 300 === 0) {
// Every 5 seconds
raceSpeed = Math.min(12, raceSpeed + 0.1);
}
};
formula one race car 2d redbull vertical. In-Game asset. 2d. High contrast. No shadows
mclaren f1 race car vertical. In-Game asset. 2d. High contrast. No shadows
f1 grand prix barriers vertical 2d. In-Game asset. 2d. High contrast. No shadows
bird's-eye view of the F1 spectator crowd. In-Game asset. 2d. High contrast. No shadows
f1 mercedes car 2d vertical. In-Game asset. 2d. High contrast. No shadows