/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var Button = Container.expand(function () { var self = Container.call(this); // Default button properties self.width = 200; self.height = 200; self.color = 0x3399FF; self.labelText = "BUTTON"; self.labelSize = 30; self.labelColor = 0xFFFFFF; // Button background self.background = null; // Create the button self.create = function () { // Remove any existing children if recreating while (self.children.length > 0) { self.removeChild(self.children[0]); } // Create background self.background = LK.getAsset('button', { anchorX: 0.5, anchorY: 0.5 }); self.background.width = self.width; self.background.height = self.height; self.background.tint = self.color; self.addChild(self.background); // Create label var label = new Text2(self.labelText, { size: self.labelSize, fill: 0x000000, bold: true }); label.anchor.set(0.5, 0.5); self.addChild(label); // Make button interactive self.interactive = true; }; // Configure button self.configure = function (options) { if (options.width !== undefined) { self.width = options.width; } if (options.height !== undefined) { self.height = options.height; } if (options.color !== undefined) { self.color = options.color; } if (options.labelText !== undefined) { self.labelText = options.labelText; } if (options.labelSize !== undefined) { self.labelSize = options.labelSize; } if (options.labelColor !== undefined) { self.labelColor = options.labelColor; } // Recreate with new properties self.create(); return self; // Allow chaining }; // Click handler self.onClick = function (callback) { self.down = function (x, y, obj) { LK.getSound('button').play(); callback(); }; return self; // Allow chaining }; // Initialize with default settings self.create(); return self; }); var Cashier = Container.expand(function () { var self = Container.call(this); // Create cashier visuals - using shopper asset with different tint var cashierGraphics = self.attachAsset('shopper', { anchorX: 0.5, anchorY: 0.5 }); // Make cashier visually distinct cashierGraphics.tint = 0x00AAFF; // Add cashier label var cashierLabel = new Text2("CASHIER", { size: 30, fill: 0x000000 }); cashierLabel.anchor.set(0.5, 0); cashierLabel.y = -120; self.addChild(cashierLabel); // Add visual indicator when all items collected self.showCheckoutReady = function () { var readyText = new Text2("CHECKOUT\nREADY!", { size: 35, fill: 0x00FF00 }); readyText.anchor.set(0.5, 0); readyText.y = -90; self.addChild(readyText); }; return self; }); var Friend = Container.expand(function () { var self = Container.call(this); // Create friend visuals - using shopper asset with friendly tint var friendGraphics = self.attachAsset('shopper', { anchorX: 0.5, anchorY: 0.5 }); // Make friend visually distinct friendGraphics.tint = 0x33CC33; // Green tint // Add friend label var friendLabel = new Text2("FRIEND", { size: 30, fill: 0x000000 }); friendLabel.anchor.set(0.5, 0); friendLabel.y = -120; self.addChild(friendLabel); // Conversation properties self.isInteracting = false; self.scenarioIndex = 0; self.scenarioTexts = [{ question: "Your friend is having a bad day. What's nicer to say?", options: ["Hi, how are you?", "Hello amigo! How are you on this wonderful day?"], correctAnswer: 1 }, { question: "Your friend got a haircut. What's nicer to say?", options: ["Nice haircut, looks good!", "Wow! Your haircut makes you look so much better than before!"], correctAnswer: 0 }, { question: "Your friend is nervous about a presentation. What's nicer to say?", options: ["Don't worry, you probably won't fail.", "You've prepared well, I believe in you!"], correctAnswer: 1 }]; // Show conversation options self.startConversation = function () { if (self.isInteracting) { return; } self.isInteracting = true; // Randomly select a scenario self.scenarioIndex = Math.floor(Math.random() * self.scenarioTexts.length); // Create conversation UI self.conversationUI = new Container(); gameArea.addChild(self.conversationUI); // Background var dialogBg = LK.getAsset('obstacle', { anchorX: 0.5, anchorY: 0.5 }); dialogBg.width = 1200; dialogBg.height = 600; dialogBg.tint = 0xFFFFFF; dialogBg.alpha = 0.9; self.conversationUI.addChild(dialogBg); // Position at center of screen self.conversationUI.x = 2048 / 2; self.conversationUI.y = 2732 / 2; // Question text var questionText = new Text2(self.scenarioTexts[self.scenarioIndex].question, { size: 40, fill: 0x000000 }); questionText.anchor.set(0.5, 0); questionText.y = -200; self.conversationUI.addChild(questionText); // Option buttons for (var i = 0; i < 2; i++) { var optionButton = new Container(); var optionBg = LK.getAsset('button', { anchorX: 0.5, anchorY: 0.5 }); optionBg.width = 300; optionBg.height = 300; optionBg.tint = 0x3399FF; optionButton.addChild(optionBg); var optionText = new Text2(self.scenarioTexts[self.scenarioIndex].options[i], { size: 30, fill: 0xFFFFFF }); optionText.anchor.set(0.5, 0.5); optionButton.addChild(optionText); optionButton.y = -50 + i * 150; optionButton.interactive = true; optionButton.optionIndex = i; optionButton.down = function (x, y, obj) { self.checkAnswer(obj.optionIndex); }; self.conversationUI.addChild(optionButton); } // Pause the game gameStarted = false; }; // Check if the selected answer is correct self.checkAnswer = function (selectedOption) { var correctOption = self.scenarioTexts[self.scenarioIndex].correctAnswer; var resultText; if (selectedOption === correctOption) { resultText = new Text2("Correct! That was a nice thing to say!", { size: 40, fill: 0x009900 }); LK.setScore(LK.getScore() + 50); showMessage("NICE RESPONSE! +50 POINTS", 2000); } else { resultText = new Text2("That wasn't the nicest option.", { size: 40, fill: 0xFF0000 }); showMessage("TRY BEING NICER NEXT TIME!", 2000); } resultText.anchor.set(0.5, 0); resultText.y = 100; self.conversationUI.addChild(resultText); // Add continue button var continueButton = new Container(); var continueBg = LK.getAsset('button', { anchorX: 0.5, anchorY: 0.5 }); continueBg.width = 200; continueBg.height = 200; continueBg.tint = 0x00AA00; continueButton.addChild(continueBg); var continueText = new Text2("CONTINUE", { size: 30, fill: 0xFFFFFF }); continueText.anchor.set(0.5, 0.5); continueButton.addChild(continueText); continueButton.y = 200; continueButton.interactive = true; continueButton.down = function () { self.endConversation(); }; self.conversationUI.addChild(continueButton); }; // End the conversation and resume the game self.endConversation = function () { if (self.conversationUI) { gameArea.removeChild(self.conversationUI); self.conversationUI = null; } self.isInteracting = false; gameStarted = true; }; return self; }); var Item = Container.expand(function () { var self = Container.call(this); var itemGraphics = self.attachAsset('item', { anchorX: 0.5, anchorY: 0.5 }); self.name = ''; self.price = 0; self.onList = false; self.isSpecial = false; self.collected = false; self.setValue = function (name, price, onList) { self.name = name; self.price = price; self.onList = onList; var nameText = new Text2(name, { size: 36, fill: 0x000000, bold: true }); nameText.anchor.set(0.5, 0.5); nameText.y = -40; self.addChild(nameText); var priceText = new Text2('$' + price.toFixed(2), { size: 20, fill: 0x008000 }); priceText.anchor.set(0.5, 0.5); priceText.y = 40; self.addChild(priceText); if (onList) { itemGraphics.tint = 0xFFFF00; // Highlight items on the list } }; self.makeSpecialDeal = function () { if (self.isSpecial) { return; } self.isSpecial = true; // Replace with special deal graphics if (self.children.indexOf(itemGraphics) !== -1) { self.removeChild(itemGraphics); } var dealGraphics = self.attachAsset('specialDeal', { anchorX: 0.5, anchorY: 0.5 }); // Apply discount self.price = Math.round(self.price * 0.5 * 100) / 100; // Update price text for (var i = 0; i < self.children.length; i++) { if (self.children[i] instanceof Text2 && self.children[i].y === 40) { self.children[i].setText('$' + self.price.toFixed(2) + ' 50% OFF!'); self.children[i].tint = 0xFF0000; break; } } }; self.collect = function () { self.collected = true; self.visible = false; }; return self; }); var KarenMode = Container.expand(function () { var self = Container.call(this); self.active = false; self.demonSpawnRate = 5000; // Much faster demon spawn rate self.shopperSpawnRate = 3000; // Much faster shopper spawn rate self.demonTimer = null; self.shopperTimer = null; // Visual indicator for Karen Mode var karenLabel = new Text2("KAREN MODE ACTIVE", { size: 40, fill: 0xFF0000 }); karenLabel.anchor.set(0.5, 0.5); self.addChild(karenLabel); // Enable Karen Mode self.enable = function (gameAreaRef, cashierRef) { if (self.active) { return; } self.active = true; self.gameArea = gameAreaRef; self.cashier = cashierRef; // Create walls for Karen mode createKarenWalls(self.gameArea); // Start rapid demon spawning self.demonTimer = LK.setInterval(function () { if (self.gameArea && self.active) { spawnFastDemon(self.gameArea, self.cashier); } }, self.demonSpawnRate); // Start rapid shopper spawning self.shopperTimer = LK.setInterval(function () { if (self.gameArea && self.active) { spawnAggressiveShopper(self.gameArea); } }, self.shopperSpawnRate); showMessage("KAREN MODE ACTIVATED!", 3000); LK.effects.flashScreen(0xFF00FF, 1000); }; // Disable Karen Mode self.disable = function () { self.active = false; if (self.demonTimer) { LK.clearInterval(self.demonTimer); self.demonTimer = null; } if (self.shopperTimer) { LK.clearInterval(self.shopperTimer); self.shopperTimer = null; } }; // Spawn a fast demon (used internally) function spawnFastDemon(gameArea, cashier) { var newDemon = new ShoppingDemon(); // Random position on screen edge var side = Math.floor(Math.random() * 4); switch (side) { case 0: // Top newDemon.x = Math.random() * 2048; newDemon.y = 50; break; case 1: // Right newDemon.x = 2048 - 50; newDemon.y = Math.random() * 2732; break; case 2: // Bottom newDemon.x = Math.random() * 2048; newDemon.y = 2732 - 50; break; case 3: // Left newDemon.x = 50; newDemon.y = Math.random() * 2732; break; } // Make Karen demons faster newDemon.speed = 4; // Twice as fast as normal demons // Set cashier target newDemon.setTarget(cashier); // Initialize tracking properties newDemon.lastX = newDemon.x; newDemon.lastY = newDemon.y; // Add to game gameArea.addChild(newDemon); demons.push(newDemon); } // Spawn an aggressive shopper (used internally) // Create walls for Karen mode function createKarenWalls(gameArea) { // Create horizontal and vertical walls at different positions // Create 3 horizontal walls for (var i = 0; i < 3; i++) { var horizontalWall = new Wall(); horizontalWall.x = 400 + i * 600; horizontalWall.y = 500 + i * 700; horizontalWall.width = 400; horizontalWall.height = 80; horizontalWall.children[0].width = 400; horizontalWall.children[0].height = 80; gameArea.addChild(horizontalWall); obstacles.push(horizontalWall); } // Create 3 vertical walls for (var j = 0; j < 3; j++) { var verticalWall = new Wall(); verticalWall.x = 300 + j * 700; verticalWall.y = 800 + j * 300; verticalWall.width = 80; verticalWall.height = 400; verticalWall.children[0].width = 80; verticalWall.children[0].height = 400; gameArea.addChild(verticalWall); obstacles.push(verticalWall); } showMessage("KAREN MODE: WALLS ADDED!", 2000); } function spawnAggressiveShopper(gameArea) { var shopper = new Shopper(); // Position at random edge var side = Math.floor(Math.random() * 4); switch (side) { case 0: // Top shopper.x = Math.random() * 2048; shopper.y = 100; break; case 1: // Right shopper.x = 2048 - 100; shopper.y = Math.random() * 2732; break; case 2: // Bottom shopper.x = Math.random() * 2048; shopper.y = 2732 - 100; break; case 3: // Left shopper.x = 100; shopper.y = Math.random() * 2732; break; } // Set shopper to be aggressive (moves faster and directly towards player) shopper.isAggressive = true; // Initialize tracking shopper.lastX = shopper.x; shopper.lastY = shopper.y; // Add to game gameArea.addChild(shopper); shoppers.push(shopper); // Start movement immediately shopper.startMoving(); } return self; }); var MainMenu = Container.expand(function () { var self = Container.call(this); // Create menu background var menuBg = LK.getAsset('obstacle', { anchorX: 0, anchorY: 0 }); menuBg.width = 1500; menuBg.height = 1800; menuBg.tint = 0xFFFFFF; menuBg.alpha = 0.8; self.addChild(menuBg); // Center the menu on screen self.x = 2048 / 2 - 750; // Half the typical menu width self.y = 2732 / 2 - 900; // Half the typical menu height // Add logo var logo = self.attachAsset('logo', { anchorX: 0.5, anchorY: 0.5 }); logo.x = menuBg.width / 2; logo.y = 300; logo.scale.set(0.4); // Scale down the logo to fit properly // Add title var titleText = new Text2("WALMART DASH", { size: 80, fill: 0x008000 }); titleText.anchor.set(0.5, 0.5); titleText.x = menuBg.width / 2; titleText.y = 550; self.addChild(titleText); // Add subtitle var subtitleText = new Text2("Race against the clock and avoid other shoppers!", { size: 40, fill: 0x000000 }); subtitleText.anchor.set(0.5, 0.5); subtitleText.x = menuBg.width / 2; subtitleText.y = 650; self.addChild(subtitleText); // Add play button var playButton = new Container(); var playBg = LK.getAsset('button', { anchorX: 0.5, anchorY: 0.5 }); playBg.width = 250; playBg.height = 250; playBg.tint = 0x00AA00; playButton.addChild(playBg); var playText = new Text2("PLAY", { size: 60, fill: 0xFFFFFF }); playText.anchor.set(0.5, 0.5); playButton.addChild(playText); playButton.x = menuBg.width / 2; playButton.y = 900; playButton.interactive = true; self.addChild(playButton); // Add instructions var instructionsText = new Text2("How to play:\n\n⢠Collect all items on your shopping list\n⢠Avoid other shoppers and obstacles\n⢠Get to the cashier before time runs out\n⢠Defeat shopping demons before they reach the cashier\n⢠Try KAREN MODE for extra challenge and points!", { size: 35, fill: 0x000000 }); instructionsText.anchor.set(0.5, 0); instructionsText.x = menuBg.width / 2; instructionsText.y = 1100; self.addChild(instructionsText); // Add event listeners playButton.down = function (x, y, obj) { LK.getSound('button').play(); self.onPlayClicked(); }; self.onPlayClicked = function () { // This will be set from outside }; return self; }); var MiniMap = Container.expand(function () { var self = Container.call(this); // Create minimap background var mapBg = LK.getAsset('obstacle', { anchorX: 0, anchorY: 0 }); mapBg.width = 300; mapBg.height = 225; mapBg.tint = 0xDDDDDD; mapBg.alpha = 0.7; self.addChild(mapBg); // Container for map items self.mapItems = new Container(); self.addChild(self.mapItems); // Factor to scale actual positions to minimap size self.scaleX = 300 / 2048; self.scaleY = 225 / 2732; // Update minimap with current game state self.update = function (cart, shoppers, items, obstacles, aisles) { // Clear previous map items while (self.mapItems.children.length > 0) { self.mapItems.removeChild(self.mapItems.children[0]); } // Draw aisles for (var i = 0; i < aisles.length; i++) { var aisleIcon = LK.getAsset('aisle', { anchorX: 0.5, anchorY: 0.5 }); aisleIcon.x = aisles[i].x * self.scaleX; aisleIcon.y = aisles[i].y * self.scaleY; aisleIcon.width = 12; aisleIcon.height = 225; aisleIcon.alpha = 0.6; self.mapItems.addChild(aisleIcon); } // Draw items on list that haven't been collected for (var j = 0; j < items.length; j++) { if (items[j].onList && !items[j].collected) { var itemIcon = LK.getAsset('item', { anchorX: 0.5, anchorY: 0.5 }); itemIcon.x = items[j].x * self.scaleX; itemIcon.y = items[j].y * self.scaleY; itemIcon.width = 8; itemIcon.height = 8; itemIcon.tint = 0xFFFF00; self.mapItems.addChild(itemIcon); } } // Draw obstacles for (var k = 0; k < obstacles.length; k++) { var obstacleIcon = LK.getAsset('obstacle', { anchorX: 0.5, anchorY: 0.5 }); obstacleIcon.x = obstacles[k].x * self.scaleX; obstacleIcon.y = obstacles[k].y * self.scaleY; obstacleIcon.width = 10; obstacleIcon.height = 10; obstacleIcon.tint = 0xFF0000; self.mapItems.addChild(obstacleIcon); } // Draw shoppers for (var m = 0; m < shoppers.length; m++) { var shopperIcon = LK.getAsset('shopper', { anchorX: 0.5, anchorY: 0.5 }); shopperIcon.x = shoppers[m].x * self.scaleX; shopperIcon.y = shoppers[m].y * self.scaleY; shopperIcon.width = 6; shopperIcon.height = 12; shopperIcon.tint = 0xFF0000; self.mapItems.addChild(shopperIcon); } // Draw cart (player) last so it's on top if (cart) { var cartIcon = LK.getAsset('cart', { anchorX: 0.5, anchorY: 0.5 }); cartIcon.x = cart.x * self.scaleX; cartIcon.y = cart.y * self.scaleY; cartIcon.width = 15; cartIcon.height = 15; cartIcon.tint = 0x0000FF; self.mapItems.addChild(cartIcon); } }; return self; }); var Obstacle = Container.expand(function () { var self = Container.call(this); var obstacleGraphics = self.attachAsset('obstacle', { anchorX: 0.5, anchorY: 0.5 }); var warningText = new Text2('ā ļø', { size: 32, fill: 0x000000 }); warningText.anchor.set(0.5, 0.5); self.addChild(warningText); self.duration = 5000; // 5 seconds self.createTime = Date.now(); self.update = function () { // Check if obstacle should be removed if (Date.now() - self.createTime > self.duration) { self.readyToRemove = true; } }; return self; }); var ObstacleRemover = Container.expand(function () { var self = Container.call(this); // Create visual element for the remover var removerGraphics = self.attachAsset('powerup', { anchorX: 0.5, anchorY: 0.5 }); // Make it visually distinct removerGraphics.tint = 0x00FF00; // Green tint // Add label var removerLabel = new Text2("REMOVER", { size: 20, fill: 0xFFFFFF }); removerLabel.anchor.set(0.5, 0.5); removerLabel.y = 40; self.addChild(removerLabel); // Tracking properties self.active = true; self.lastX = 0; self.lastY = 0; // Animation properties self.floatOffset = 0; self.floatSpeed = 0.05; // Update method for animation self.update = function () { // Keep track of last position self.lastX = self.x; self.lastY = self.y; // Simple floating animation self.floatOffset += self.floatSpeed; self.y += Math.sin(self.floatOffset) * 0.5; }; return self; }); var PowerUp = Container.expand(function () { var self = Container.call(this); // Create power-up visual var powerUpGraphics = self.attachAsset('item', { anchorX: 0.5, anchorY: 0.5 }); // Make it visually distinct powerUpGraphics.tint = 0x00FFFF; self.type = 'speed'; // Default type self.duration = 5000; // Default duration: 5 seconds self.active = false; self.collected = false; // Set power-up type and properties self.setType = function (type) { self.type = type; var typeText = new Text2(type.toUpperCase(), { size: 20, fill: 0xFFFFFF }); typeText.anchor.set(0.5, 0.5); self.addChild(typeText); // Set appearance based on type switch (type) { case 'speed': powerUpGraphics.tint = 0x00FFFF; // Cyan self.duration = 5000; break; case 'magnet': powerUpGraphics.tint = 0xFF00FF; // Magenta self.duration = 7000; break; case 'shield': powerUpGraphics.tint = 0x0000FF; // Blue self.duration = 10000; break; } }; self.collect = function () { self.collected = true; self.visible = false; }; return self; }); var Shopper = Container.expand(function () { var self = Container.call(this); var shopperGraphics = self.attachAsset('shopper', { anchorX: 0.5, anchorY: 0.5 }); self.isMoving = false; self.startX = 0; self.startY = 0; self.targetX = 0; self.targetY = 0; self.lastX = 0; self.lastY = 0; // Initialize shopper movement self.startMoving = function () { // Stop any existing movement tween.stop(self); // Remember starting position self.startX = self.x; self.startY = self.y; self.lastX = self.x; self.lastY = self.y; // Choose a random destination within the aisle area var randomDistance = 100 + Math.random() * 300; var randomAngle = Math.random() * Math.PI * 2; self.targetX = self.x + Math.cos(randomAngle) * randomDistance; self.targetY = self.y + Math.sin(randomAngle) * randomDistance; // Constrain to game boundaries self.targetX = Math.max(100, Math.min(2048 - 100, self.targetX)); self.targetY = Math.max(100, Math.min(2732 - 100, self.targetY)); // Randomize movement duration (2-5 seconds) var duration = 2000 + Math.random() * 3000; // Use tween to smoothly move to the destination tween(self, { x: self.targetX, y: self.targetY }, { duration: duration, easing: tween.easeInOut, onFinish: function onFinish() { // Wait for a moment before moving again LK.setTimeout(function () { self.startMoving(); }, 500 + Math.random() * 1000); } }); self.isMoving = true; }; self.update = function () { // Keep track of last position for intersection detection self.lastX = self.x; self.lastY = self.y; // If in aggressive mode, target the player directly if (self.isAggressive && cart) { // Target player directly instead of random movement var dx = cart.x - self.x; var dy = cart.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Move toward player with increased speed if (distance > 10) { self.x += dx / distance * 4; // Faster aggressive speed self.y += dy / distance * 4; // Rotate to face direction of movement if (self.children[0]) { self.children[0].rotation = Math.atan2(dy, dx) + Math.PI / 2; } } self.isMoving = true; } else { // Start regular moving if not already moving if (!self.isMoving) { self.startMoving(); } } }; return self; }); var ShoppingCart = Container.expand(function () { var self = Container.call(this); var cartGraphics = self.attachAsset('cart', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 5; self.budget = 100; self.collectedItems = []; self.isColliding = false; self.powerUps = {}; self.magnetRange = 0; // Visually indicate active power-ups self.powerUpIndicator = new Container(); self.addChild(self.powerUpIndicator); self.powerUpIndicator.y = -100; self.setBudget = function (amount) { self.budget = amount; }; self.addItem = function (item) { if (self.budget >= item.price) { self.budget -= item.price; self.collectedItems.push(item.name); return true; } return false; }; // Apply a power-up effect self.applyPowerUp = function (type, duration) { // Clear previous power-up of this type if active if (self.powerUps[type] && self.powerUps[type].timer) { LK.clearTimeout(self.powerUps[type].timer); } // Set up power-up self.powerUps[type] = { active: true, startTime: Date.now(), duration: duration }; // Apply immediate effects switch (type) { case 'speed': self.originalSpeed = self.speed; self.speed = 10; // Double speed break; case 'magnet': self.magnetRange = 200; // Set item attraction range break; case 'shield': // Visual indicator if (!self.shieldGraphics) { self.shieldGraphics = LK.getAsset('item', { anchorX: 0.5, anchorY: 0.5, width: 200, height: 200 }); self.shieldGraphics.tint = 0x0000FF; self.shieldGraphics.alpha = 0.3; self.addChildAt(self.shieldGraphics, 0); } else { self.shieldGraphics.visible = true; } break; } // Update indicator self.updatePowerUpIndicator(); // Set timer to remove power-up self.powerUps[type].timer = LK.setTimeout(function () { self.removePowerUp(type); }, duration); }; // Remove power-up effect self.removePowerUp = function (type) { if (!self.powerUps[type] || !self.powerUps[type].active) { return; } // Remove effects switch (type) { case 'speed': self.speed = self.originalSpeed; break; case 'magnet': self.magnetRange = 0; break; case 'shield': if (self.shieldGraphics) { self.shieldGraphics.visible = false; } break; } // Update status self.powerUps[type].active = false; self.updatePowerUpIndicator(); }; // Update the visual indicator of active power-ups self.updatePowerUpIndicator = function () { // Clear current indicators while (self.powerUpIndicator.children.length > 0) { self.powerUpIndicator.removeChild(self.powerUpIndicator.children[0]); } // Add indicators for active power-ups var xPos = 0; var types = Object.keys(self.powerUps); for (var i = 0; i < types.length; i++) { if (self.powerUps[types[i]] && self.powerUps[types[i]].active) { // Calculate remaining time var elapsed = Date.now() - self.powerUps[types[i]].startTime; var remaining = Math.max(0, self.powerUps[types[i]].duration - elapsed); var seconds = Math.ceil(remaining / 1000); var indicator = new Text2(types[i] + ": " + seconds + "s", { size: 20, fill: 0xFFFFFF }); indicator.anchor.set(0.5, 0); indicator.x = xPos; self.powerUpIndicator.addChild(indicator); xPos += 100; } } }; return self; }); var ShoppingDemon = Container.expand(function () { var self = Container.call(this); // Create demon visuals - using shopper asset with demonic tint var demonGraphics = self.attachAsset('shopper', { anchorX: 0.5, anchorY: 0.5 }); // Make demon visually distinct with red tint demonGraphics.tint = 0xFF0000; // Scale up the demon slightly to make it more threatening demonGraphics.scaleX = 1.2; demonGraphics.scaleY = 1.2; // Add demon label var demonLabel = new Text2("DEMON", { size: 30, fill: 0xFF0000 }); demonLabel.anchor.set(0.5, 0); demonLabel.y = -120; self.addChild(demonLabel); // Movement properties self.speed = 2; self.target = null; self.cashier = null; self.lastX = 0; self.lastY = 0; self.active = true; // Set cashier target self.setTarget = function (cashier) { self.cashier = cashier; }; // Update demon movement self.update = function () { // Keep track of last position self.lastX = self.x; self.lastY = self.y; // If we have a cashier target, move toward it if (self.cashier && self.active) { // Calculate direction to cashier var dx = self.cashier.x - self.x; var dy = self.cashier.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Move toward cashier if not already there if (distance > 10) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; // Rotate demon to face direction of movement demonGraphics.rotation = Math.atan2(dy, dx) + Math.PI / 2; } } }; // Flash demon when hit self.hit = function () { LK.effects.flashObject(self, 0xFFFFFF, 300); }; return self; }); var Wall = Container.expand(function () { var self = Container.call(this); // Create wall visual var wallGraphics = self.attachAsset('obstacle', { anchorX: 0.5, anchorY: 0.5 }); // Make walls visually distinct wallGraphics.tint = 0xFF0000; // Red tint for walls wallGraphics.alpha = 0.8; // Warning text var warningText = new Text2('WALL', { size: 24, fill: 0xFFFFFF }); warningText.anchor.set(0.5, 0.5); self.addChild(warningText); // Add update method to fix the TypeError self.update = function () { // Walls are static, so no update logic needed // Just adding the method to prevent errors }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xFFFFFF // White background }); /**** * Game Code ****/ // Game state variables var gameStarted = false; var timeRemaining = 60; // seconds var level = 1; var lives = 3; // Player lives var shoppingList = []; var aisles = []; var shoppers = []; var items = []; var powerUps = []; var obstacles = []; var specialDealTimer = null; var cleanupTimer = null; var powerUpUpdateTimer = null; var demonTimer = null; var friendTimer = null; var demons = []; var friends = []; var cart; var gameArea; var demon; var mainMenu; var showingMainMenu = true; // UI elements var budgetText, timerText, livesText, shoppingListText, levelText, messageText, miniMap; // Create game area (container for all game elements) gameArea = new Container(); game.addChild(gameArea); // Create main menu function createMainMenu() { mainMenu = new MainMenu(); game.addChild(mainMenu); // Set event handler for play button mainMenu.onPlayClicked = function () { // Hide menu and start game showingMainMenu = false; mainMenu.visible = false; // Initialize game level initLevel(); }; } // Create the main menu createMainMenu(); // Initialize level function initLevel() { // Hide main menu if visible if (mainMenu && mainMenu.visible) { mainMenu.visible = false; showingMainMenu = false; } // Clear previous level elements clearLevel(); // Set level properties timeRemaining = 60 + level * 10; // Reset lives if starting from level 1 if (level === 1) { lives = 3; } // Create aisles (4 aisles) for (var i = 0; i < 5; i++) { var aisle = LK.getAsset('aisle', { anchorX: 0.5, anchorY: 0.5 }); aisle.x = 100 + i * 450; aisle.y = 2732 / 2; gameArea.addChild(aisle); aisles.push(aisle); } // Generate shopping list (3-7 items depending on level) var numItems = 3 + Math.min(level, 4); generateShoppingList(numItems); // Create items in store createStoreItems(); // Create shoppers (obstacles) var numShoppers = 3 + level; createShoppers(numShoppers); // Create player's cart cart = new ShoppingCart(); cart.x = 2048 / 2; cart.y = 2732 - 200; cart.setBudget(100 + level * 20); gameArea.addChild(cart); // Create power-ups createPowerUps(); // Set up timers for special events setupEventTimers(); // Update UI updateUI(); // Create cashier at the bottom of store var cashier = new Cashier(); cashier.x = 2048 / 2; cashier.y = 2732 - 100; gameArea.addChild(cashier); // Create shopping demon var demon = new ShoppingDemon(); demon.x = 100; demon.y = 200; demon.setTarget(cashier); gameArea.addChild(demon); // Store reference to shopping demon game.demon = demon; // Create Karen Mode button var karenButton = new Container(); var karenBg = LK.getAsset('button', { anchorX: 0.5, anchorY: 0.5 }); karenBg.width = 200; karenBg.height = 200; karenBg.tint = 0xFF00FF; // Purple for Karen mode karenButton.addChild(karenBg); var karenText = new Text2("KAREN MODE", { size: 36, fill: 0xFFFFFF }); karenText.anchor.set(0.5, 0.5); karenButton.addChild(karenText); karenButton.x = 2048 - 160; karenButton.y = 2732 - 320; karenButton.interactive = true; gameArea.addChild(karenButton); // Create Karen mode controller game.karenMode = new KarenMode(); game.karenMode.x = 2048 / 2; game.karenMode.y = 100; game.karenMode.visible = false; gameArea.addChild(game.karenMode); // Karen mode button handler karenButton.down = function () { LK.getSound('button').play(); if (!game.karenMode.active) { game.karenMode.visible = true; game.karenMode.enable(gameArea, cashier); karenText.setText("NORMAL MODE"); karenBg.tint = 0x00AA00; // Green for normal mode // Add bonus points for activating Karen mode LK.setScore(LK.getScore() + 100); } else { game.karenMode.visible = false; game.karenMode.disable(); karenText.setText("KAREN MODE"); karenBg.tint = 0xFF00FF; // Purple for Karen mode } }; // Start the game gameStarted = true; LK.playMusic('bgmusic'); } function clearLevel() { // Clear all arrays aisles = []; shoppers = []; items = []; obstacles = []; shoppingList = []; demons = []; friends = []; demon = null; game.demon = null; // Clean up Karen Mode if active if (game.karenMode && game.karenMode.active) { game.karenMode.disable(); } game.karenMode = null; // Clear all timers if (specialDealTimer) { LK.clearInterval(specialDealTimer); specialDealTimer = null; } if (cleanupTimer) { LK.clearInterval(cleanupTimer); cleanupTimer = null; } if (demonTimer) { LK.clearInterval(demonTimer); demonTimer = null; } if (friendTimer) { LK.clearInterval(friendTimer); friendTimer = null; } // Remove all children from game area while (gameArea.children.length > 0) { gameArea.removeChild(gameArea.children[0]); } } function generateShoppingList(numItems) { var possibleItems = [{ name: "Milk", price: 3.49 }, { name: "Bread", price: 2.99 }, { name: "Eggs", price: 4.29 }, { name: "Cereal", price: 4.99 }, { name: "Bananas", price: 1.99 }, { name: "Chips", price: 3.79 }, { name: "Soda", price: 5.49 }, { name: "Pizza", price: 7.99 }, { name: "Ice Cream", price: 6.49 }, { name: "Chicken", price: 8.99 }]; // Shuffle array for (var i = possibleItems.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var temp = possibleItems[i]; possibleItems[i] = possibleItems[j]; possibleItems[j] = temp; } // Take first numItems shoppingList = possibleItems.slice(0, numItems); // Update shopping list display updateShoppingListText(); } function createStoreItems() { // Create items from shopping list for (var i = 0; i < shoppingList.length; i++) { var item = new Item(); var aisleIndex = Math.floor(Math.random() * aisles.length); var aisle = aisles[aisleIndex]; item.x = aisle.x + (Math.random() * 200 - 100); item.y = 200 + Math.random() * (2732 - 400); // Make sure item doesn't spawn inside a wall var validPosition = false; var attempts = 0; while (!validPosition && attempts < 10) { validPosition = true; // Check if item would intersect with any obstacle for (var w = 0; w < obstacles.length; w++) { if (Math.abs(item.x - obstacles[w].x) < 100 && Math.abs(item.y - obstacles[w].y) < 100) { validPosition = false; // Try a different position item.x = aisle.x + (Math.random() * 200 - 100); item.y = 200 + Math.random() * (2732 - 400); break; } } attempts++; } item.setValue(shoppingList[i].name, shoppingList[i].price, true); gameArea.addChild(item); items.push(item); } // Create additional random items not on list var numExtraItems = 10 + level * 2; var possibleItems = [{ name: "Apples", price: 3.99 }, { name: "Soup", price: 2.49 }, { name: "Yogurt", price: 4.79 }, { name: "Coffee", price: 7.99 }, { name: "Pasta", price: 1.99 }, { name: "Candy", price: 3.29 }, { name: "Juice", price: 4.49 }, { name: "Paper", price: 5.99 }, { name: "Soap", price: 3.59 }, { name: "Cookies", price: 4.29 }, { name: "Cheese", price: 5.49 }, { name: "Water", price: 3.99 }, { name: "Tissues", price: 2.79 }, { name: "Detergent", price: 8.99 }, { name: "Toothpaste", price: 3.99 }]; for (var j = 0; j < numExtraItems; j++) { var extraItem = new Item(); var extraAisleIndex = Math.floor(Math.random() * aisles.length); var extraAisle = aisles[extraAisleIndex]; var itemData = possibleItems[Math.floor(Math.random() * possibleItems.length)]; extraItem.x = extraAisle.x + (Math.random() * 200 - 100); extraItem.y = 200 + Math.random() * (2732 - 400); // Make sure extra item doesn't spawn inside a wall var validPosition = false; var attempts = 0; while (!validPosition && attempts < 10) { validPosition = true; // Check if item would intersect with any obstacle for (var w = 0; w < obstacles.length; w++) { if (Math.abs(extraItem.x - obstacles[w].x) < 100 && Math.abs(extraItem.y - obstacles[w].y) < 100) { validPosition = false; // Try a different position extraItem.x = extraAisle.x + (Math.random() * 200 - 100); extraItem.y = 200 + Math.random() * (2732 - 400); break; } } attempts++; } extraItem.setValue(itemData.name, itemData.price, false); gameArea.addChild(extraItem); items.push(extraItem); } // Create obstacle remover tool var obstacleRemover = new ObstacleRemover(); // Position it behind an obstacle var randomObstacleIndex = 0; // First obstacle created will have this index LK.setTimeout(function () { if (obstacles.length > 0) { obstacleRemover.x = obstacles[randomObstacleIndex].x; obstacleRemover.y = obstacles[randomObstacleIndex].y + 20; // Slightly behind obstacleRemover.lastX = obstacleRemover.x; obstacleRemover.lastY = obstacleRemover.y; gameArea.addChild(obstacleRemover); // Make sure the remover is visually behind the obstacle gameArea.removeChild(obstacles[randomObstacleIndex]); gameArea.addChild(obstacles[randomObstacleIndex]); // Show hint message showMessage("Find the REMOVER behind the obstacle!", 3000); } }, 5000); // Wait 5 seconds after level start } function createShoppers(numShoppers) { for (var i = 0; i < numShoppers; i++) { var shopper = new Shopper(); var aisleIndex = Math.floor(Math.random() * aisles.length); var aisle = aisles[aisleIndex]; shopper.x = aisle.x + (Math.random() * 200 - 100); shopper.y = 200 + Math.random() * (2732 - 400); shopperGraphics = shopper.children[0]; // Randomly rotate the shopper shopperGraphics.rotation = Math.random() * Math.PI * 2; gameArea.addChild(shopper); shoppers.push(shopper); // Stagger the movement starts LK.setTimeout(function (s) { return function () { s.startMoving(); }; }(shopper), i * 300); } } function setupEventTimers() { // Special deals event (every 10-15 seconds) specialDealTimer = LK.setInterval(function () { createSpecialDeal(); }, 10000 + Math.random() * 5000); // Cleanup event (every 15-20 seconds) cleanupTimer = LK.setInterval(function () { createCleanupObstacle(); }, 15000 + Math.random() * 5000); // Demon spawner event (every 20-30 seconds) demonTimer = LK.setInterval(function () { spawnDemon(); }, 20000 + Math.random() * 10000); // Friend spawner event (every 15-25 seconds) friendTimer = LK.setInterval(function () { spawnFriend(); }, 15000 + Math.random() * 10000); } function createSpecialDeal() { // Pick a random item to make a special deal if (items.length > 0) { var randomIndex = Math.floor(Math.random() * items.length); var item = items[randomIndex]; if (!item.isSpecial && !item.collected) { item.makeSpecialDeal(); // Show flash sale message showMessage("FLASH SALE!", 2000); } } } function createCleanupObstacle() { // Create cleanup on a random aisle var randomAisleIndex = Math.floor(Math.random() * aisles.length); var aisle = aisles[randomAisleIndex]; var obstacle = new Obstacle(); obstacle.x = aisle.x; obstacle.y = 500 + Math.random() * 1500; gameArea.addChild(obstacle); obstacles.push(obstacle); // Show cleanup message showMessage("CLEANUP ON AISLE " + (randomAisleIndex + 1), 2000); } function updateShoppingListText() { var listText = "Shopping List:\n"; var remainingItems = 0; for (var i = 0; i < shoppingList.length; i++) { var collected = false; // Check if item has been collected if (cart && cart.collectedItems) { collected = cart.collectedItems.indexOf(shoppingList[i].name) !== -1; } if (!collected) { remainingItems++; listText += "⢠" + shoppingList[i].name + " - $" + shoppingList[i].price.toFixed(2) + "\n"; } else { listText += "ā " + shoppingList[i].name + " - $" + shoppingList[i].price.toFixed(2) + "\n"; } } if (shoppingListText) { shoppingListText.setText(listText); } return remainingItems; } function showMessage(text, duration) { if (messageText) { messageText.setText(text); messageText.visible = true; LK.setTimeout(function () { messageText.visible = false; }, duration); } } function createUI() { // Create budget display budgetText = new Text2("Budget: $0.00", { size: 40, fill: 0x008000 }); budgetText.anchor.set(0, 0); LK.gui.topRight.addChild(budgetText); // Create timer display timerText = new Text2("Time: 0:00", { size: 40, fill: 0xDE2027 }); timerText.anchor.set(0.5, 0); LK.gui.top.addChild(timerText); // Create lives display livesText = new Text2("Lives: 3", { size: 40, fill: 0xFF6600 }); livesText.anchor.set(0.5, 0); livesText.y = 50; // Position below timer LK.gui.top.addChild(livesText); // Create level display levelText = new Text2("Level: 1", { size: 40, fill: 0x0071CE }); levelText.anchor.set(1, 0); LK.gui.topLeft.addChild(levelText); // Move it a bit to the right to avoid the platform menu levelText.x += 120; // Create shopping list display shoppingListText = new Text2("Shopping List:", { size: 30, fill: 0x000000 }); shoppingListText.anchor.set(1, 0); shoppingListText.x = -20; // Move it a bit to the left to ensure visibility LK.gui.right.addChild(shoppingListText); // Create message text for events messageText = new Text2("", { size: 60, fill: 0xFF0000 }); messageText.anchor.set(0.5, 0.5); messageText.visible = false; LK.gui.center.addChild(messageText); // Create minimap in bottom left corner miniMap = new MiniMap(); miniMap.x = 20; miniMap.y = -250; // Position from bottom LK.gui.bottomLeft.addChild(miniMap); } function updateUI() { if (!cart) { return; } // Update budget display budgetText.setText("Budget: $" + cart.budget.toFixed(2)); // Update timer display var minutes = Math.floor(timeRemaining / 60); var seconds = timeRemaining % 60; timerText.setText("Time: " + minutes + ":" + (seconds < 10 ? "0" : "") + seconds); // Update lives display livesText.setText("Lives: " + lives); // Update level display levelText.setText("Level: " + level); // Update shopping list var remainingItems = updateShoppingListText(); // If no items remaining, highlight cashier to indicate checkout if (remainingItems === 0) { // Find cashier and set indicator for (var c = 0; c < gameArea.children.length; c++) { if (gameArea.children[c] instanceof Cashier) { gameArea.children[c].showCheckoutReady(); break; } } // No longer win automatically - need to go to cashier } } // Handle touch/drag controls var dragging = false; function handleMove(x, y, obj) { if (dragging && cart) { // Move the cart to the touch position cart.x = x; cart.y = y; // Keep within game boundaries cart.x = Math.max(cart.width / 2, Math.min(2048 - cart.width / 2, cart.x)); cart.y = Math.max(cart.height / 2, Math.min(2732 - cart.height / 2, cart.y)); } } game.move = handleMove; game.down = function (x, y, obj) { if (showingMainMenu) { // Let the menu handle its own click events return; } if (!gameStarted) { initLevel(); return; } if (cart && Math.abs(x - cart.x) < cart.width / 2 && Math.abs(y - cart.y) < cart.height / 2) { dragging = true; } handleMove(x, y, obj); }; game.up = function (x, y, obj) { dragging = false; }; // Main game loop var lastSecondTick = 0; game.update = function () { if (showingMainMenu) { // Don't update game while showing menu return; } if (!gameStarted) { return; } // Update timer once per second if (LK.ticks % 60 === 0) { timeRemaining--; updateUI(); // Check if time is up if (timeRemaining <= 0) { gameStarted = false; showMessage("TIME'S UP!", 2000); LK.setTimeout(function () { LK.showGameOver(); }, 2000); } } // Update power-up indicators every 16 frames (roughly 4 times per second) if (cart && LK.ticks % 15 === 0) { cart.updatePowerUpIndicator(); // Update minimap if (miniMap) { miniMap.update(cart, shoppers, items, obstacles, aisles); } } // Apply magnet effect if active if (cart && cart.powerUps.magnet && cart.powerUps.magnet.active && cart.magnetRange > 0) { for (var i = 0; i < items.length; i++) { var item = items[i]; if (!item.collected && !(item instanceof PowerUp)) { var dx = cart.x - item.x; var dy = cart.y - item.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < cart.magnetRange) { // Move item toward cart var speed = 5 + (1 - distance / cart.magnetRange) * 10; item.x += dx / distance * speed; item.y += dy / distance * speed; } } } } // Update all shoppers for (var i = 0; i < shoppers.length; i++) { shoppers[i].update(); } // Update all obstacles for (var j = obstacles.length - 1; j >= 0; j--) { obstacles[j].update(); if (obstacles[j].readyToRemove) { gameArea.removeChild(obstacles[j]); obstacles.splice(j, 1); } } // Check for collisions with cart if (cart) { // Check for collisions with items for (var k = 0; k < items.length; k++) { var item = items[k]; if (!item.collected && cart.intersects(item)) { // Check if item is a power-up if (item instanceof PowerUp) { // Collect power-up item.collect(); LK.getSound('pickup').play(); LK.setScore(LK.getScore() + 15); // Apply the power-up effect cart.applyPowerUp(item.type, item.duration); showMessage(item.type.toUpperCase() + " POWER-UP!", 1000); continue; } if (item.onList) { // Collect item on shopping list if (cart.addItem(item)) { item.collect(); LK.getSound('pickup').play(); LK.setScore(LK.getScore() + 10); updateUI(); } else { // Not enough budget if (!cart.isColliding) { showMessage("Not enough budget!", 1000); cart.isColliding = true; LK.setTimeout(function () { cart.isColliding = false; }, 1000); } } } else if (item.isSpecial) { // Collect special item if (cart.addItem(item)) { item.collect(); LK.getSound('deal').play(); LK.setScore(LK.getScore() + 5); updateUI(); } else { // Not enough budget if (!cart.isColliding) { showMessage("Not enough budget!", 1000); cart.isColliding = true; LK.setTimeout(function () { cart.isColliding = false; }, 1000); } } } } } // Check for collisions with shoppers for (var m = 0; m < shoppers.length; m++) { if (cart.intersects(shoppers[m]) && !cart.isColliding) { // Check if shield power-up is active if (cart.powerUps.shield && cart.powerUps.shield.active) { // Shield protects from collision LK.getSound('collision').play(); LK.effects.flashObject(cart, 0x0000FF, 500); // Blue flash for shield cart.isColliding = true; showMessage("SHIELD PROTECTED YOU!", 1000); // Move shopper away var dx = shoppers[m].x - cart.x; var dy = shoppers[m].y - cart.y; var distance = Math.sqrt(dx * dx + dy * dy); shoppers[m].x += dx / distance * 150; shoppers[m].y += dy / distance * 150; LK.setTimeout(function () { cart.isColliding = false; }, 1000); } else { // Collision with shopper - Game Over! LK.getSound('collision').play(); LK.effects.flashObject(cart, 0xFF0000, 500); cart.isColliding = true; // Show death message and end game showMessage("GAME OVER! Other shopper crashed into you!", 2000); gameStarted = false; updateUI(); LK.setTimeout(function () { LK.showGameOver(); // Show main menu after game over LK.setTimeout(function () { mainMenu.visible = true; showingMainMenu = true; }, 1000); }, 2000); } } } // Check for collisions with obstacles for (var n = 0; n < obstacles.length; n++) { if (cart.intersects(obstacles[n]) && !cart.isColliding) { // Collision with obstacle LK.getSound('collision').play(); LK.effects.flashObject(cart, 0xFF0000, 500); cart.isColliding = true; // Lose a life instead of time penalty lives--; updateUI(); // Check if out of lives if (lives <= 0) { // Game over when no lives left showMessage("OUT OF LIVES! GAME OVER!", 2000); gameStarted = false; LK.setTimeout(function () { LK.showGameOver(); }, 2000); } else { // Show message about losing a life showMessage("OUCH! " + lives + " LIVES LEFT!", 1000); LK.setTimeout(function () { cart.isColliding = false; }, 1000); } } } // Update and check collision with demon from initial level if (game.demon && game.demon.active) { // Update demon game.demon.update(); // Check if demon collided with cashier for (var c = 0; c < gameArea.children.length; c++) { if (gameArea.children[c] instanceof Cashier && game.demon.intersects(gameArea.children[c]) && game.demon.active) { // Demon reached cashier - Game Over! LK.getSound('collision').play(); LK.effects.flashScreen(0xFF0000, 1000); showMessage("GAME OVER! Demon reached the cashier!", 2000); gameStarted = false; LK.setTimeout(function () { LK.showGameOver(); }, 2000); return; } } // Check if player touched the demon if (cart && cart.intersects(game.demon) && !cart.isColliding) { // Player touched demon - defeat it! game.demon.hit(); game.demon.active = false; LK.getSound('pickup').play(); LK.setScore(LK.getScore() + 50); showMessage("DEMON DEFEATED! +50 POINTS", 2000); // Make demon disappear slowly tween(game.demon, { alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { gameArea.removeChild(game.demon); } }); } } // Update and check collision with spawned demons for (var d = demons.length - 1; d >= 0; d--) { var currentDemon = demons[d]; // Update demon if (currentDemon.active) { currentDemon.update(); // Check if demon collided with cashier for (var c = 0; c < gameArea.children.length; c++) { if (gameArea.children[c] instanceof Cashier && currentDemon.intersects(gameArea.children[c]) && currentDemon.active) { // Demon reached cashier - Game Over! LK.getSound('collision').play(); LK.effects.flashScreen(0xFF0000, 1000); showMessage("GAME OVER! Demon reached the cashier!", 2000); gameStarted = false; LK.setTimeout(function () { LK.showGameOver(); }, 2000); return; } } // Check if player touched the demon if (cart && cart.intersects(currentDemon) && !cart.isColliding) { // Player touched demon - defeat it! currentDemon.hit(); currentDemon.active = false; LK.getSound('pickup').play(); LK.setScore(LK.getScore() + 50); showMessage("DEMON DEFEATED! +50 POINTS", 2000); // Make demon disappear slowly tween(currentDemon, { alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 1000, easing: tween.easeOut, onFinish: function (demonIndex) { return function () { if (demons[demonIndex]) { gameArea.removeChild(demons[demonIndex]); demons.splice(demonIndex, 1); } }; }(d) }); // Set cart as colliding briefly to prevent multiple demon hits cart.isColliding = true; LK.setTimeout(function () { cart.isColliding = false; }, 500); } } } // Check for collisions with walls in Karen mode for (var w = 0; w < obstacles.length; w++) { if (obstacles[w] instanceof Wall && cart.intersects(obstacles[w]) && !cart.isColliding) { // Collision with wall LK.getSound('collision').play(); LK.effects.flashObject(cart, 0xFF0000, 500); cart.isColliding = true; // Lose a life for hitting a wall lives--; updateUI(); // Check if out of lives if (lives <= 0) { // Game over when no lives left showMessage("OUT OF LIVES! GAME OVER!", 2000); gameStarted = false; LK.setTimeout(function () { LK.showGameOver(); }, 2000); } else { // Show message about losing a life showMessage("HIT A WALL! " + lives + " LIVES LEFT!", 1000); LK.setTimeout(function () { cart.isColliding = false; }, 1000); } } } // Update and check collision with obstacle remover for (var r = 0; r < gameArea.children.length; r++) { if (gameArea.children[r] instanceof ObstacleRemover && cart.intersects(gameArea.children[r])) { var remover = gameArea.children[r]; if (remover.active) { // Found the remover! Use it to remove obstacles remover.active = false; // Visual effect for remover pickup LK.getSound('pickup').play(); LK.effects.flashObject(remover, 0x00FF00, 500); // Show message showMessage("OBSTACLE REMOVER FOUND! Cleaning up...", 2000); // Remove all obstacles with animation for (var o = 0; o < obstacles.length; o++) { (function (obstacle, index) { tween(obstacle, { alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { // Bonus points for each obstacle removed LK.setScore(LK.getScore() + 10); } }); })(obstacles[o], o); } // Clear obstacles array after animation LK.setTimeout(function () { while (obstacles.length > 0) { if (obstacles[0].parent) { gameArea.removeChild(obstacles[0]); } obstacles.splice(0, 1); } }, 1100); // Remove the remover with animation tween(remover, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { gameArea.removeChild(remover); } }); } } // Update any obstacle removers (for animation) if (gameArea.children[r] instanceof ObstacleRemover) { gameArea.children[r].update(); } } // Check for collision with friends if (friends && friends.length > 0) { for (var f = 0; f < friends.length; f++) { if (cart && cart.intersects(friends[f]) && !cart.isColliding && !friends[f].isInteracting) { // Start conversation with friend friends[f].startConversation(); cart.isColliding = true; // Set timeout to reset collision status after conversation LK.setTimeout(function () { cart.isColliding = false; }, 1000); } } } // Check for collision with cashier for level completion for (var c = 0; c < gameArea.children.length; c++) { if (gameArea.children[c] instanceof Cashier && cart.intersects(gameArea.children[c])) { // Check if all items have been collected var remainingItems = updateShoppingListText(); if (remainingItems === 0 && !cart.isColliding) { // Level complete! level++; showMessage("CHECKOUT COMPLETE! LEVEL UP!", 3000); LK.setTimeout(initLevel, 3000); gameStarted = false; // Award bonus points for remaining time var timeBonus = timeRemaining * 2; LK.setScore(LK.getScore() + timeBonus); break; } else if (remainingItems > 0 && !cart.isColliding) { // Still have items to collect showMessage("Finish your shopping first!", 1000); cart.isColliding = true; LK.setTimeout(function () { cart.isColliding = false; }, 1000); } } } } }; // Initialize UI createUI(); // Don't show start instructions since we now have a menu function createPowerUps() { // Create 3 power-ups of different types, randomly placed in aisles var powerUpTypes = ['speed', 'magnet', 'shield']; for (var i = 0; i < powerUpTypes.length; i++) { var powerUp = new PowerUp(); var aisleIndex = Math.floor(Math.random() * aisles.length); var aisle = aisles[aisleIndex]; powerUp.x = aisle.x + (Math.random() * 200 - 100); powerUp.y = 200 + Math.random() * (2732 - 400); // Make sure power-up doesn't spawn inside a wall var validPosition = false; var attempts = 0; while (!validPosition && attempts < 10) { validPosition = true; // Check if power-up would intersect with any obstacle for (var w = 0; w < obstacles.length; w++) { if (Math.abs(powerUp.x - obstacles[w].x) < 100 && Math.abs(powerUp.y - obstacles[w].y) < 100) { validPosition = false; // Try a different position powerUp.x = aisle.x + (Math.random() * 200 - 100); powerUp.y = 200 + Math.random() * (2732 - 400); break; } } attempts++; } powerUp.setType(powerUpTypes[i]); gameArea.addChild(powerUp); items.push(powerUp); // Add to items array for collision detection } } function spawnFriend() { if (!gameStarted) { return; } // Create a new friend var newFriend = new Friend(); // Place friend at a random location in the store var aisleIndex = Math.floor(Math.random() * aisles.length); var aisle = aisles[aisleIndex]; newFriend.x = aisle.x + (Math.random() * 200 - 100); newFriend.y = 200 + Math.random() * (2732 - 400); // Initialize tracking properties newFriend.lastX = newFriend.x; newFriend.lastY = newFriend.y; // Add to game gameArea.addChild(newFriend); // Add to a friends array if (!friends) { friends = []; } friends.push(newFriend); // Show message showMessage("A FRIEND IS NEARBY! GO SAY HI!", 2000); } function spawnDemon() { if (!gameStarted) { return; } // Create a new demon var newDemon = new ShoppingDemon(); // Place demon at a random edge of the screen var side = Math.floor(Math.random() * 4); switch (side) { case 0: // Top newDemon.x = Math.random() * 2048; newDemon.y = 50; break; case 1: // Right newDemon.x = 2048 - 50; newDemon.y = Math.random() * 2732; break; case 2: // Bottom newDemon.x = Math.random() * 2048; newDemon.y = 2732 - 50; break; case 3: // Left newDemon.x = 50; newDemon.y = Math.random() * 2732; break; } // Find the cashier to target for (var c = 0; c < gameArea.children.length; c++) { if (gameArea.children[c] instanceof Cashier) { newDemon.setTarget(gameArea.children[c]); break; } } // Initialize tracking properties newDemon.lastX = newDemon.x; newDemon.lastY = newDemon.y; // Add to game gameArea.addChild(newDemon); demons.push(newDemon); // Show warning message showMessage("SHOPPING DEMON APPROACHING!", 2000); }
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Button = Container.expand(function () {
var self = Container.call(this);
// Default button properties
self.width = 200;
self.height = 200;
self.color = 0x3399FF;
self.labelText = "BUTTON";
self.labelSize = 30;
self.labelColor = 0xFFFFFF;
// Button background
self.background = null;
// Create the button
self.create = function () {
// Remove any existing children if recreating
while (self.children.length > 0) {
self.removeChild(self.children[0]);
}
// Create background
self.background = LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5
});
self.background.width = self.width;
self.background.height = self.height;
self.background.tint = self.color;
self.addChild(self.background);
// Create label
var label = new Text2(self.labelText, {
size: self.labelSize,
fill: 0x000000,
bold: true
});
label.anchor.set(0.5, 0.5);
self.addChild(label);
// Make button interactive
self.interactive = true;
};
// Configure button
self.configure = function (options) {
if (options.width !== undefined) {
self.width = options.width;
}
if (options.height !== undefined) {
self.height = options.height;
}
if (options.color !== undefined) {
self.color = options.color;
}
if (options.labelText !== undefined) {
self.labelText = options.labelText;
}
if (options.labelSize !== undefined) {
self.labelSize = options.labelSize;
}
if (options.labelColor !== undefined) {
self.labelColor = options.labelColor;
}
// Recreate with new properties
self.create();
return self; // Allow chaining
};
// Click handler
self.onClick = function (callback) {
self.down = function (x, y, obj) {
LK.getSound('button').play();
callback();
};
return self; // Allow chaining
};
// Initialize with default settings
self.create();
return self;
});
var Cashier = Container.expand(function () {
var self = Container.call(this);
// Create cashier visuals - using shopper asset with different tint
var cashierGraphics = self.attachAsset('shopper', {
anchorX: 0.5,
anchorY: 0.5
});
// Make cashier visually distinct
cashierGraphics.tint = 0x00AAFF;
// Add cashier label
var cashierLabel = new Text2("CASHIER", {
size: 30,
fill: 0x000000
});
cashierLabel.anchor.set(0.5, 0);
cashierLabel.y = -120;
self.addChild(cashierLabel);
// Add visual indicator when all items collected
self.showCheckoutReady = function () {
var readyText = new Text2("CHECKOUT\nREADY!", {
size: 35,
fill: 0x00FF00
});
readyText.anchor.set(0.5, 0);
readyText.y = -90;
self.addChild(readyText);
};
return self;
});
var Friend = Container.expand(function () {
var self = Container.call(this);
// Create friend visuals - using shopper asset with friendly tint
var friendGraphics = self.attachAsset('shopper', {
anchorX: 0.5,
anchorY: 0.5
});
// Make friend visually distinct
friendGraphics.tint = 0x33CC33; // Green tint
// Add friend label
var friendLabel = new Text2("FRIEND", {
size: 30,
fill: 0x000000
});
friendLabel.anchor.set(0.5, 0);
friendLabel.y = -120;
self.addChild(friendLabel);
// Conversation properties
self.isInteracting = false;
self.scenarioIndex = 0;
self.scenarioTexts = [{
question: "Your friend is having a bad day. What's nicer to say?",
options: ["Hi, how are you?", "Hello amigo! How are you on this wonderful day?"],
correctAnswer: 1
}, {
question: "Your friend got a haircut. What's nicer to say?",
options: ["Nice haircut, looks good!", "Wow! Your haircut makes you look so much better than before!"],
correctAnswer: 0
}, {
question: "Your friend is nervous about a presentation. What's nicer to say?",
options: ["Don't worry, you probably won't fail.", "You've prepared well, I believe in you!"],
correctAnswer: 1
}];
// Show conversation options
self.startConversation = function () {
if (self.isInteracting) {
return;
}
self.isInteracting = true;
// Randomly select a scenario
self.scenarioIndex = Math.floor(Math.random() * self.scenarioTexts.length);
// Create conversation UI
self.conversationUI = new Container();
gameArea.addChild(self.conversationUI);
// Background
var dialogBg = LK.getAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
dialogBg.width = 1200;
dialogBg.height = 600;
dialogBg.tint = 0xFFFFFF;
dialogBg.alpha = 0.9;
self.conversationUI.addChild(dialogBg);
// Position at center of screen
self.conversationUI.x = 2048 / 2;
self.conversationUI.y = 2732 / 2;
// Question text
var questionText = new Text2(self.scenarioTexts[self.scenarioIndex].question, {
size: 40,
fill: 0x000000
});
questionText.anchor.set(0.5, 0);
questionText.y = -200;
self.conversationUI.addChild(questionText);
// Option buttons
for (var i = 0; i < 2; i++) {
var optionButton = new Container();
var optionBg = LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5
});
optionBg.width = 300;
optionBg.height = 300;
optionBg.tint = 0x3399FF;
optionButton.addChild(optionBg);
var optionText = new Text2(self.scenarioTexts[self.scenarioIndex].options[i], {
size: 30,
fill: 0xFFFFFF
});
optionText.anchor.set(0.5, 0.5);
optionButton.addChild(optionText);
optionButton.y = -50 + i * 150;
optionButton.interactive = true;
optionButton.optionIndex = i;
optionButton.down = function (x, y, obj) {
self.checkAnswer(obj.optionIndex);
};
self.conversationUI.addChild(optionButton);
}
// Pause the game
gameStarted = false;
};
// Check if the selected answer is correct
self.checkAnswer = function (selectedOption) {
var correctOption = self.scenarioTexts[self.scenarioIndex].correctAnswer;
var resultText;
if (selectedOption === correctOption) {
resultText = new Text2("Correct! That was a nice thing to say!", {
size: 40,
fill: 0x009900
});
LK.setScore(LK.getScore() + 50);
showMessage("NICE RESPONSE! +50 POINTS", 2000);
} else {
resultText = new Text2("That wasn't the nicest option.", {
size: 40,
fill: 0xFF0000
});
showMessage("TRY BEING NICER NEXT TIME!", 2000);
}
resultText.anchor.set(0.5, 0);
resultText.y = 100;
self.conversationUI.addChild(resultText);
// Add continue button
var continueButton = new Container();
var continueBg = LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5
});
continueBg.width = 200;
continueBg.height = 200;
continueBg.tint = 0x00AA00;
continueButton.addChild(continueBg);
var continueText = new Text2("CONTINUE", {
size: 30,
fill: 0xFFFFFF
});
continueText.anchor.set(0.5, 0.5);
continueButton.addChild(continueText);
continueButton.y = 200;
continueButton.interactive = true;
continueButton.down = function () {
self.endConversation();
};
self.conversationUI.addChild(continueButton);
};
// End the conversation and resume the game
self.endConversation = function () {
if (self.conversationUI) {
gameArea.removeChild(self.conversationUI);
self.conversationUI = null;
}
self.isInteracting = false;
gameStarted = true;
};
return self;
});
var Item = Container.expand(function () {
var self = Container.call(this);
var itemGraphics = self.attachAsset('item', {
anchorX: 0.5,
anchorY: 0.5
});
self.name = '';
self.price = 0;
self.onList = false;
self.isSpecial = false;
self.collected = false;
self.setValue = function (name, price, onList) {
self.name = name;
self.price = price;
self.onList = onList;
var nameText = new Text2(name, {
size: 36,
fill: 0x000000,
bold: true
});
nameText.anchor.set(0.5, 0.5);
nameText.y = -40;
self.addChild(nameText);
var priceText = new Text2('$' + price.toFixed(2), {
size: 20,
fill: 0x008000
});
priceText.anchor.set(0.5, 0.5);
priceText.y = 40;
self.addChild(priceText);
if (onList) {
itemGraphics.tint = 0xFFFF00; // Highlight items on the list
}
};
self.makeSpecialDeal = function () {
if (self.isSpecial) {
return;
}
self.isSpecial = true;
// Replace with special deal graphics
if (self.children.indexOf(itemGraphics) !== -1) {
self.removeChild(itemGraphics);
}
var dealGraphics = self.attachAsset('specialDeal', {
anchorX: 0.5,
anchorY: 0.5
});
// Apply discount
self.price = Math.round(self.price * 0.5 * 100) / 100;
// Update price text
for (var i = 0; i < self.children.length; i++) {
if (self.children[i] instanceof Text2 && self.children[i].y === 40) {
self.children[i].setText('$' + self.price.toFixed(2) + ' 50% OFF!');
self.children[i].tint = 0xFF0000;
break;
}
}
};
self.collect = function () {
self.collected = true;
self.visible = false;
};
return self;
});
var KarenMode = Container.expand(function () {
var self = Container.call(this);
self.active = false;
self.demonSpawnRate = 5000; // Much faster demon spawn rate
self.shopperSpawnRate = 3000; // Much faster shopper spawn rate
self.demonTimer = null;
self.shopperTimer = null;
// Visual indicator for Karen Mode
var karenLabel = new Text2("KAREN MODE ACTIVE", {
size: 40,
fill: 0xFF0000
});
karenLabel.anchor.set(0.5, 0.5);
self.addChild(karenLabel);
// Enable Karen Mode
self.enable = function (gameAreaRef, cashierRef) {
if (self.active) {
return;
}
self.active = true;
self.gameArea = gameAreaRef;
self.cashier = cashierRef;
// Create walls for Karen mode
createKarenWalls(self.gameArea);
// Start rapid demon spawning
self.demonTimer = LK.setInterval(function () {
if (self.gameArea && self.active) {
spawnFastDemon(self.gameArea, self.cashier);
}
}, self.demonSpawnRate);
// Start rapid shopper spawning
self.shopperTimer = LK.setInterval(function () {
if (self.gameArea && self.active) {
spawnAggressiveShopper(self.gameArea);
}
}, self.shopperSpawnRate);
showMessage("KAREN MODE ACTIVATED!", 3000);
LK.effects.flashScreen(0xFF00FF, 1000);
};
// Disable Karen Mode
self.disable = function () {
self.active = false;
if (self.demonTimer) {
LK.clearInterval(self.demonTimer);
self.demonTimer = null;
}
if (self.shopperTimer) {
LK.clearInterval(self.shopperTimer);
self.shopperTimer = null;
}
};
// Spawn a fast demon (used internally)
function spawnFastDemon(gameArea, cashier) {
var newDemon = new ShoppingDemon();
// Random position on screen edge
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
newDemon.x = Math.random() * 2048;
newDemon.y = 50;
break;
case 1:
// Right
newDemon.x = 2048 - 50;
newDemon.y = Math.random() * 2732;
break;
case 2:
// Bottom
newDemon.x = Math.random() * 2048;
newDemon.y = 2732 - 50;
break;
case 3:
// Left
newDemon.x = 50;
newDemon.y = Math.random() * 2732;
break;
}
// Make Karen demons faster
newDemon.speed = 4; // Twice as fast as normal demons
// Set cashier target
newDemon.setTarget(cashier);
// Initialize tracking properties
newDemon.lastX = newDemon.x;
newDemon.lastY = newDemon.y;
// Add to game
gameArea.addChild(newDemon);
demons.push(newDemon);
}
// Spawn an aggressive shopper (used internally)
// Create walls for Karen mode
function createKarenWalls(gameArea) {
// Create horizontal and vertical walls at different positions
// Create 3 horizontal walls
for (var i = 0; i < 3; i++) {
var horizontalWall = new Wall();
horizontalWall.x = 400 + i * 600;
horizontalWall.y = 500 + i * 700;
horizontalWall.width = 400;
horizontalWall.height = 80;
horizontalWall.children[0].width = 400;
horizontalWall.children[0].height = 80;
gameArea.addChild(horizontalWall);
obstacles.push(horizontalWall);
}
// Create 3 vertical walls
for (var j = 0; j < 3; j++) {
var verticalWall = new Wall();
verticalWall.x = 300 + j * 700;
verticalWall.y = 800 + j * 300;
verticalWall.width = 80;
verticalWall.height = 400;
verticalWall.children[0].width = 80;
verticalWall.children[0].height = 400;
gameArea.addChild(verticalWall);
obstacles.push(verticalWall);
}
showMessage("KAREN MODE: WALLS ADDED!", 2000);
}
function spawnAggressiveShopper(gameArea) {
var shopper = new Shopper();
// Position at random edge
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
shopper.x = Math.random() * 2048;
shopper.y = 100;
break;
case 1:
// Right
shopper.x = 2048 - 100;
shopper.y = Math.random() * 2732;
break;
case 2:
// Bottom
shopper.x = Math.random() * 2048;
shopper.y = 2732 - 100;
break;
case 3:
// Left
shopper.x = 100;
shopper.y = Math.random() * 2732;
break;
}
// Set shopper to be aggressive (moves faster and directly towards player)
shopper.isAggressive = true;
// Initialize tracking
shopper.lastX = shopper.x;
shopper.lastY = shopper.y;
// Add to game
gameArea.addChild(shopper);
shoppers.push(shopper);
// Start movement immediately
shopper.startMoving();
}
return self;
});
var MainMenu = Container.expand(function () {
var self = Container.call(this);
// Create menu background
var menuBg = LK.getAsset('obstacle', {
anchorX: 0,
anchorY: 0
});
menuBg.width = 1500;
menuBg.height = 1800;
menuBg.tint = 0xFFFFFF;
menuBg.alpha = 0.8;
self.addChild(menuBg);
// Center the menu on screen
self.x = 2048 / 2 - 750; // Half the typical menu width
self.y = 2732 / 2 - 900; // Half the typical menu height
// Add logo
var logo = self.attachAsset('logo', {
anchorX: 0.5,
anchorY: 0.5
});
logo.x = menuBg.width / 2;
logo.y = 300;
logo.scale.set(0.4); // Scale down the logo to fit properly
// Add title
var titleText = new Text2("WALMART DASH", {
size: 80,
fill: 0x008000
});
titleText.anchor.set(0.5, 0.5);
titleText.x = menuBg.width / 2;
titleText.y = 550;
self.addChild(titleText);
// Add subtitle
var subtitleText = new Text2("Race against the clock and avoid other shoppers!", {
size: 40,
fill: 0x000000
});
subtitleText.anchor.set(0.5, 0.5);
subtitleText.x = menuBg.width / 2;
subtitleText.y = 650;
self.addChild(subtitleText);
// Add play button
var playButton = new Container();
var playBg = LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5
});
playBg.width = 250;
playBg.height = 250;
playBg.tint = 0x00AA00;
playButton.addChild(playBg);
var playText = new Text2("PLAY", {
size: 60,
fill: 0xFFFFFF
});
playText.anchor.set(0.5, 0.5);
playButton.addChild(playText);
playButton.x = menuBg.width / 2;
playButton.y = 900;
playButton.interactive = true;
self.addChild(playButton);
// Add instructions
var instructionsText = new Text2("How to play:\n\n⢠Collect all items on your shopping list\n⢠Avoid other shoppers and obstacles\n⢠Get to the cashier before time runs out\n⢠Defeat shopping demons before they reach the cashier\n⢠Try KAREN MODE for extra challenge and points!", {
size: 35,
fill: 0x000000
});
instructionsText.anchor.set(0.5, 0);
instructionsText.x = menuBg.width / 2;
instructionsText.y = 1100;
self.addChild(instructionsText);
// Add event listeners
playButton.down = function (x, y, obj) {
LK.getSound('button').play();
self.onPlayClicked();
};
self.onPlayClicked = function () {
// This will be set from outside
};
return self;
});
var MiniMap = Container.expand(function () {
var self = Container.call(this);
// Create minimap background
var mapBg = LK.getAsset('obstacle', {
anchorX: 0,
anchorY: 0
});
mapBg.width = 300;
mapBg.height = 225;
mapBg.tint = 0xDDDDDD;
mapBg.alpha = 0.7;
self.addChild(mapBg);
// Container for map items
self.mapItems = new Container();
self.addChild(self.mapItems);
// Factor to scale actual positions to minimap size
self.scaleX = 300 / 2048;
self.scaleY = 225 / 2732;
// Update minimap with current game state
self.update = function (cart, shoppers, items, obstacles, aisles) {
// Clear previous map items
while (self.mapItems.children.length > 0) {
self.mapItems.removeChild(self.mapItems.children[0]);
}
// Draw aisles
for (var i = 0; i < aisles.length; i++) {
var aisleIcon = LK.getAsset('aisle', {
anchorX: 0.5,
anchorY: 0.5
});
aisleIcon.x = aisles[i].x * self.scaleX;
aisleIcon.y = aisles[i].y * self.scaleY;
aisleIcon.width = 12;
aisleIcon.height = 225;
aisleIcon.alpha = 0.6;
self.mapItems.addChild(aisleIcon);
}
// Draw items on list that haven't been collected
for (var j = 0; j < items.length; j++) {
if (items[j].onList && !items[j].collected) {
var itemIcon = LK.getAsset('item', {
anchorX: 0.5,
anchorY: 0.5
});
itemIcon.x = items[j].x * self.scaleX;
itemIcon.y = items[j].y * self.scaleY;
itemIcon.width = 8;
itemIcon.height = 8;
itemIcon.tint = 0xFFFF00;
self.mapItems.addChild(itemIcon);
}
}
// Draw obstacles
for (var k = 0; k < obstacles.length; k++) {
var obstacleIcon = LK.getAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
obstacleIcon.x = obstacles[k].x * self.scaleX;
obstacleIcon.y = obstacles[k].y * self.scaleY;
obstacleIcon.width = 10;
obstacleIcon.height = 10;
obstacleIcon.tint = 0xFF0000;
self.mapItems.addChild(obstacleIcon);
}
// Draw shoppers
for (var m = 0; m < shoppers.length; m++) {
var shopperIcon = LK.getAsset('shopper', {
anchorX: 0.5,
anchorY: 0.5
});
shopperIcon.x = shoppers[m].x * self.scaleX;
shopperIcon.y = shoppers[m].y * self.scaleY;
shopperIcon.width = 6;
shopperIcon.height = 12;
shopperIcon.tint = 0xFF0000;
self.mapItems.addChild(shopperIcon);
}
// Draw cart (player) last so it's on top
if (cart) {
var cartIcon = LK.getAsset('cart', {
anchorX: 0.5,
anchorY: 0.5
});
cartIcon.x = cart.x * self.scaleX;
cartIcon.y = cart.y * self.scaleY;
cartIcon.width = 15;
cartIcon.height = 15;
cartIcon.tint = 0x0000FF;
self.mapItems.addChild(cartIcon);
}
};
return self;
});
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obstacleGraphics = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
var warningText = new Text2('ā ļø', {
size: 32,
fill: 0x000000
});
warningText.anchor.set(0.5, 0.5);
self.addChild(warningText);
self.duration = 5000; // 5 seconds
self.createTime = Date.now();
self.update = function () {
// Check if obstacle should be removed
if (Date.now() - self.createTime > self.duration) {
self.readyToRemove = true;
}
};
return self;
});
var ObstacleRemover = Container.expand(function () {
var self = Container.call(this);
// Create visual element for the remover
var removerGraphics = self.attachAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5
});
// Make it visually distinct
removerGraphics.tint = 0x00FF00; // Green tint
// Add label
var removerLabel = new Text2("REMOVER", {
size: 20,
fill: 0xFFFFFF
});
removerLabel.anchor.set(0.5, 0.5);
removerLabel.y = 40;
self.addChild(removerLabel);
// Tracking properties
self.active = true;
self.lastX = 0;
self.lastY = 0;
// Animation properties
self.floatOffset = 0;
self.floatSpeed = 0.05;
// Update method for animation
self.update = function () {
// Keep track of last position
self.lastX = self.x;
self.lastY = self.y;
// Simple floating animation
self.floatOffset += self.floatSpeed;
self.y += Math.sin(self.floatOffset) * 0.5;
};
return self;
});
var PowerUp = Container.expand(function () {
var self = Container.call(this);
// Create power-up visual
var powerUpGraphics = self.attachAsset('item', {
anchorX: 0.5,
anchorY: 0.5
});
// Make it visually distinct
powerUpGraphics.tint = 0x00FFFF;
self.type = 'speed'; // Default type
self.duration = 5000; // Default duration: 5 seconds
self.active = false;
self.collected = false;
// Set power-up type and properties
self.setType = function (type) {
self.type = type;
var typeText = new Text2(type.toUpperCase(), {
size: 20,
fill: 0xFFFFFF
});
typeText.anchor.set(0.5, 0.5);
self.addChild(typeText);
// Set appearance based on type
switch (type) {
case 'speed':
powerUpGraphics.tint = 0x00FFFF; // Cyan
self.duration = 5000;
break;
case 'magnet':
powerUpGraphics.tint = 0xFF00FF; // Magenta
self.duration = 7000;
break;
case 'shield':
powerUpGraphics.tint = 0x0000FF; // Blue
self.duration = 10000;
break;
}
};
self.collect = function () {
self.collected = true;
self.visible = false;
};
return self;
});
var Shopper = Container.expand(function () {
var self = Container.call(this);
var shopperGraphics = self.attachAsset('shopper', {
anchorX: 0.5,
anchorY: 0.5
});
self.isMoving = false;
self.startX = 0;
self.startY = 0;
self.targetX = 0;
self.targetY = 0;
self.lastX = 0;
self.lastY = 0;
// Initialize shopper movement
self.startMoving = function () {
// Stop any existing movement
tween.stop(self);
// Remember starting position
self.startX = self.x;
self.startY = self.y;
self.lastX = self.x;
self.lastY = self.y;
// Choose a random destination within the aisle area
var randomDistance = 100 + Math.random() * 300;
var randomAngle = Math.random() * Math.PI * 2;
self.targetX = self.x + Math.cos(randomAngle) * randomDistance;
self.targetY = self.y + Math.sin(randomAngle) * randomDistance;
// Constrain to game boundaries
self.targetX = Math.max(100, Math.min(2048 - 100, self.targetX));
self.targetY = Math.max(100, Math.min(2732 - 100, self.targetY));
// Randomize movement duration (2-5 seconds)
var duration = 2000 + Math.random() * 3000;
// Use tween to smoothly move to the destination
tween(self, {
x: self.targetX,
y: self.targetY
}, {
duration: duration,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Wait for a moment before moving again
LK.setTimeout(function () {
self.startMoving();
}, 500 + Math.random() * 1000);
}
});
self.isMoving = true;
};
self.update = function () {
// Keep track of last position for intersection detection
self.lastX = self.x;
self.lastY = self.y;
// If in aggressive mode, target the player directly
if (self.isAggressive && cart) {
// Target player directly instead of random movement
var dx = cart.x - self.x;
var dy = cart.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Move toward player with increased speed
if (distance > 10) {
self.x += dx / distance * 4; // Faster aggressive speed
self.y += dy / distance * 4;
// Rotate to face direction of movement
if (self.children[0]) {
self.children[0].rotation = Math.atan2(dy, dx) + Math.PI / 2;
}
}
self.isMoving = true;
} else {
// Start regular moving if not already moving
if (!self.isMoving) {
self.startMoving();
}
}
};
return self;
});
var ShoppingCart = Container.expand(function () {
var self = Container.call(this);
var cartGraphics = self.attachAsset('cart', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 5;
self.budget = 100;
self.collectedItems = [];
self.isColliding = false;
self.powerUps = {};
self.magnetRange = 0;
// Visually indicate active power-ups
self.powerUpIndicator = new Container();
self.addChild(self.powerUpIndicator);
self.powerUpIndicator.y = -100;
self.setBudget = function (amount) {
self.budget = amount;
};
self.addItem = function (item) {
if (self.budget >= item.price) {
self.budget -= item.price;
self.collectedItems.push(item.name);
return true;
}
return false;
};
// Apply a power-up effect
self.applyPowerUp = function (type, duration) {
// Clear previous power-up of this type if active
if (self.powerUps[type] && self.powerUps[type].timer) {
LK.clearTimeout(self.powerUps[type].timer);
}
// Set up power-up
self.powerUps[type] = {
active: true,
startTime: Date.now(),
duration: duration
};
// Apply immediate effects
switch (type) {
case 'speed':
self.originalSpeed = self.speed;
self.speed = 10; // Double speed
break;
case 'magnet':
self.magnetRange = 200; // Set item attraction range
break;
case 'shield':
// Visual indicator
if (!self.shieldGraphics) {
self.shieldGraphics = LK.getAsset('item', {
anchorX: 0.5,
anchorY: 0.5,
width: 200,
height: 200
});
self.shieldGraphics.tint = 0x0000FF;
self.shieldGraphics.alpha = 0.3;
self.addChildAt(self.shieldGraphics, 0);
} else {
self.shieldGraphics.visible = true;
}
break;
}
// Update indicator
self.updatePowerUpIndicator();
// Set timer to remove power-up
self.powerUps[type].timer = LK.setTimeout(function () {
self.removePowerUp(type);
}, duration);
};
// Remove power-up effect
self.removePowerUp = function (type) {
if (!self.powerUps[type] || !self.powerUps[type].active) {
return;
}
// Remove effects
switch (type) {
case 'speed':
self.speed = self.originalSpeed;
break;
case 'magnet':
self.magnetRange = 0;
break;
case 'shield':
if (self.shieldGraphics) {
self.shieldGraphics.visible = false;
}
break;
}
// Update status
self.powerUps[type].active = false;
self.updatePowerUpIndicator();
};
// Update the visual indicator of active power-ups
self.updatePowerUpIndicator = function () {
// Clear current indicators
while (self.powerUpIndicator.children.length > 0) {
self.powerUpIndicator.removeChild(self.powerUpIndicator.children[0]);
}
// Add indicators for active power-ups
var xPos = 0;
var types = Object.keys(self.powerUps);
for (var i = 0; i < types.length; i++) {
if (self.powerUps[types[i]] && self.powerUps[types[i]].active) {
// Calculate remaining time
var elapsed = Date.now() - self.powerUps[types[i]].startTime;
var remaining = Math.max(0, self.powerUps[types[i]].duration - elapsed);
var seconds = Math.ceil(remaining / 1000);
var indicator = new Text2(types[i] + ": " + seconds + "s", {
size: 20,
fill: 0xFFFFFF
});
indicator.anchor.set(0.5, 0);
indicator.x = xPos;
self.powerUpIndicator.addChild(indicator);
xPos += 100;
}
}
};
return self;
});
var ShoppingDemon = Container.expand(function () {
var self = Container.call(this);
// Create demon visuals - using shopper asset with demonic tint
var demonGraphics = self.attachAsset('shopper', {
anchorX: 0.5,
anchorY: 0.5
});
// Make demon visually distinct with red tint
demonGraphics.tint = 0xFF0000;
// Scale up the demon slightly to make it more threatening
demonGraphics.scaleX = 1.2;
demonGraphics.scaleY = 1.2;
// Add demon label
var demonLabel = new Text2("DEMON", {
size: 30,
fill: 0xFF0000
});
demonLabel.anchor.set(0.5, 0);
demonLabel.y = -120;
self.addChild(demonLabel);
// Movement properties
self.speed = 2;
self.target = null;
self.cashier = null;
self.lastX = 0;
self.lastY = 0;
self.active = true;
// Set cashier target
self.setTarget = function (cashier) {
self.cashier = cashier;
};
// Update demon movement
self.update = function () {
// Keep track of last position
self.lastX = self.x;
self.lastY = self.y;
// If we have a cashier target, move toward it
if (self.cashier && self.active) {
// Calculate direction to cashier
var dx = self.cashier.x - self.x;
var dy = self.cashier.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Move toward cashier if not already there
if (distance > 10) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
// Rotate demon to face direction of movement
demonGraphics.rotation = Math.atan2(dy, dx) + Math.PI / 2;
}
}
};
// Flash demon when hit
self.hit = function () {
LK.effects.flashObject(self, 0xFFFFFF, 300);
};
return self;
});
var Wall = Container.expand(function () {
var self = Container.call(this);
// Create wall visual
var wallGraphics = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
// Make walls visually distinct
wallGraphics.tint = 0xFF0000; // Red tint for walls
wallGraphics.alpha = 0.8;
// Warning text
var warningText = new Text2('WALL', {
size: 24,
fill: 0xFFFFFF
});
warningText.anchor.set(0.5, 0.5);
self.addChild(warningText);
// Add update method to fix the TypeError
self.update = function () {
// Walls are static, so no update logic needed
// Just adding the method to prevent errors
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xFFFFFF // White background
});
/****
* Game Code
****/
// Game state variables
var gameStarted = false;
var timeRemaining = 60; // seconds
var level = 1;
var lives = 3; // Player lives
var shoppingList = [];
var aisles = [];
var shoppers = [];
var items = [];
var powerUps = [];
var obstacles = [];
var specialDealTimer = null;
var cleanupTimer = null;
var powerUpUpdateTimer = null;
var demonTimer = null;
var friendTimer = null;
var demons = [];
var friends = [];
var cart;
var gameArea;
var demon;
var mainMenu;
var showingMainMenu = true;
// UI elements
var budgetText, timerText, livesText, shoppingListText, levelText, messageText, miniMap;
// Create game area (container for all game elements)
gameArea = new Container();
game.addChild(gameArea);
// Create main menu
function createMainMenu() {
mainMenu = new MainMenu();
game.addChild(mainMenu);
// Set event handler for play button
mainMenu.onPlayClicked = function () {
// Hide menu and start game
showingMainMenu = false;
mainMenu.visible = false;
// Initialize game level
initLevel();
};
}
// Create the main menu
createMainMenu();
// Initialize level
function initLevel() {
// Hide main menu if visible
if (mainMenu && mainMenu.visible) {
mainMenu.visible = false;
showingMainMenu = false;
}
// Clear previous level elements
clearLevel();
// Set level properties
timeRemaining = 60 + level * 10;
// Reset lives if starting from level 1
if (level === 1) {
lives = 3;
}
// Create aisles (4 aisles)
for (var i = 0; i < 5; i++) {
var aisle = LK.getAsset('aisle', {
anchorX: 0.5,
anchorY: 0.5
});
aisle.x = 100 + i * 450;
aisle.y = 2732 / 2;
gameArea.addChild(aisle);
aisles.push(aisle);
}
// Generate shopping list (3-7 items depending on level)
var numItems = 3 + Math.min(level, 4);
generateShoppingList(numItems);
// Create items in store
createStoreItems();
// Create shoppers (obstacles)
var numShoppers = 3 + level;
createShoppers(numShoppers);
// Create player's cart
cart = new ShoppingCart();
cart.x = 2048 / 2;
cart.y = 2732 - 200;
cart.setBudget(100 + level * 20);
gameArea.addChild(cart);
// Create power-ups
createPowerUps();
// Set up timers for special events
setupEventTimers();
// Update UI
updateUI();
// Create cashier at the bottom of store
var cashier = new Cashier();
cashier.x = 2048 / 2;
cashier.y = 2732 - 100;
gameArea.addChild(cashier);
// Create shopping demon
var demon = new ShoppingDemon();
demon.x = 100;
demon.y = 200;
demon.setTarget(cashier);
gameArea.addChild(demon);
// Store reference to shopping demon
game.demon = demon;
// Create Karen Mode button
var karenButton = new Container();
var karenBg = LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5
});
karenBg.width = 200;
karenBg.height = 200;
karenBg.tint = 0xFF00FF; // Purple for Karen mode
karenButton.addChild(karenBg);
var karenText = new Text2("KAREN MODE", {
size: 36,
fill: 0xFFFFFF
});
karenText.anchor.set(0.5, 0.5);
karenButton.addChild(karenText);
karenButton.x = 2048 - 160;
karenButton.y = 2732 - 320;
karenButton.interactive = true;
gameArea.addChild(karenButton);
// Create Karen mode controller
game.karenMode = new KarenMode();
game.karenMode.x = 2048 / 2;
game.karenMode.y = 100;
game.karenMode.visible = false;
gameArea.addChild(game.karenMode);
// Karen mode button handler
karenButton.down = function () {
LK.getSound('button').play();
if (!game.karenMode.active) {
game.karenMode.visible = true;
game.karenMode.enable(gameArea, cashier);
karenText.setText("NORMAL MODE");
karenBg.tint = 0x00AA00; // Green for normal mode
// Add bonus points for activating Karen mode
LK.setScore(LK.getScore() + 100);
} else {
game.karenMode.visible = false;
game.karenMode.disable();
karenText.setText("KAREN MODE");
karenBg.tint = 0xFF00FF; // Purple for Karen mode
}
};
// Start the game
gameStarted = true;
LK.playMusic('bgmusic');
}
function clearLevel() {
// Clear all arrays
aisles = [];
shoppers = [];
items = [];
obstacles = [];
shoppingList = [];
demons = [];
friends = [];
demon = null;
game.demon = null;
// Clean up Karen Mode if active
if (game.karenMode && game.karenMode.active) {
game.karenMode.disable();
}
game.karenMode = null;
// Clear all timers
if (specialDealTimer) {
LK.clearInterval(specialDealTimer);
specialDealTimer = null;
}
if (cleanupTimer) {
LK.clearInterval(cleanupTimer);
cleanupTimer = null;
}
if (demonTimer) {
LK.clearInterval(demonTimer);
demonTimer = null;
}
if (friendTimer) {
LK.clearInterval(friendTimer);
friendTimer = null;
}
// Remove all children from game area
while (gameArea.children.length > 0) {
gameArea.removeChild(gameArea.children[0]);
}
}
function generateShoppingList(numItems) {
var possibleItems = [{
name: "Milk",
price: 3.49
}, {
name: "Bread",
price: 2.99
}, {
name: "Eggs",
price: 4.29
}, {
name: "Cereal",
price: 4.99
}, {
name: "Bananas",
price: 1.99
}, {
name: "Chips",
price: 3.79
}, {
name: "Soda",
price: 5.49
}, {
name: "Pizza",
price: 7.99
}, {
name: "Ice Cream",
price: 6.49
}, {
name: "Chicken",
price: 8.99
}];
// Shuffle array
for (var i = possibleItems.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = possibleItems[i];
possibleItems[i] = possibleItems[j];
possibleItems[j] = temp;
}
// Take first numItems
shoppingList = possibleItems.slice(0, numItems);
// Update shopping list display
updateShoppingListText();
}
function createStoreItems() {
// Create items from shopping list
for (var i = 0; i < shoppingList.length; i++) {
var item = new Item();
var aisleIndex = Math.floor(Math.random() * aisles.length);
var aisle = aisles[aisleIndex];
item.x = aisle.x + (Math.random() * 200 - 100);
item.y = 200 + Math.random() * (2732 - 400);
// Make sure item doesn't spawn inside a wall
var validPosition = false;
var attempts = 0;
while (!validPosition && attempts < 10) {
validPosition = true;
// Check if item would intersect with any obstacle
for (var w = 0; w < obstacles.length; w++) {
if (Math.abs(item.x - obstacles[w].x) < 100 && Math.abs(item.y - obstacles[w].y) < 100) {
validPosition = false;
// Try a different position
item.x = aisle.x + (Math.random() * 200 - 100);
item.y = 200 + Math.random() * (2732 - 400);
break;
}
}
attempts++;
}
item.setValue(shoppingList[i].name, shoppingList[i].price, true);
gameArea.addChild(item);
items.push(item);
}
// Create additional random items not on list
var numExtraItems = 10 + level * 2;
var possibleItems = [{
name: "Apples",
price: 3.99
}, {
name: "Soup",
price: 2.49
}, {
name: "Yogurt",
price: 4.79
}, {
name: "Coffee",
price: 7.99
}, {
name: "Pasta",
price: 1.99
}, {
name: "Candy",
price: 3.29
}, {
name: "Juice",
price: 4.49
}, {
name: "Paper",
price: 5.99
}, {
name: "Soap",
price: 3.59
}, {
name: "Cookies",
price: 4.29
}, {
name: "Cheese",
price: 5.49
}, {
name: "Water",
price: 3.99
}, {
name: "Tissues",
price: 2.79
}, {
name: "Detergent",
price: 8.99
}, {
name: "Toothpaste",
price: 3.99
}];
for (var j = 0; j < numExtraItems; j++) {
var extraItem = new Item();
var extraAisleIndex = Math.floor(Math.random() * aisles.length);
var extraAisle = aisles[extraAisleIndex];
var itemData = possibleItems[Math.floor(Math.random() * possibleItems.length)];
extraItem.x = extraAisle.x + (Math.random() * 200 - 100);
extraItem.y = 200 + Math.random() * (2732 - 400);
// Make sure extra item doesn't spawn inside a wall
var validPosition = false;
var attempts = 0;
while (!validPosition && attempts < 10) {
validPosition = true;
// Check if item would intersect with any obstacle
for (var w = 0; w < obstacles.length; w++) {
if (Math.abs(extraItem.x - obstacles[w].x) < 100 && Math.abs(extraItem.y - obstacles[w].y) < 100) {
validPosition = false;
// Try a different position
extraItem.x = extraAisle.x + (Math.random() * 200 - 100);
extraItem.y = 200 + Math.random() * (2732 - 400);
break;
}
}
attempts++;
}
extraItem.setValue(itemData.name, itemData.price, false);
gameArea.addChild(extraItem);
items.push(extraItem);
}
// Create obstacle remover tool
var obstacleRemover = new ObstacleRemover();
// Position it behind an obstacle
var randomObstacleIndex = 0; // First obstacle created will have this index
LK.setTimeout(function () {
if (obstacles.length > 0) {
obstacleRemover.x = obstacles[randomObstacleIndex].x;
obstacleRemover.y = obstacles[randomObstacleIndex].y + 20; // Slightly behind
obstacleRemover.lastX = obstacleRemover.x;
obstacleRemover.lastY = obstacleRemover.y;
gameArea.addChild(obstacleRemover);
// Make sure the remover is visually behind the obstacle
gameArea.removeChild(obstacles[randomObstacleIndex]);
gameArea.addChild(obstacles[randomObstacleIndex]);
// Show hint message
showMessage("Find the REMOVER behind the obstacle!", 3000);
}
}, 5000); // Wait 5 seconds after level start
}
function createShoppers(numShoppers) {
for (var i = 0; i < numShoppers; i++) {
var shopper = new Shopper();
var aisleIndex = Math.floor(Math.random() * aisles.length);
var aisle = aisles[aisleIndex];
shopper.x = aisle.x + (Math.random() * 200 - 100);
shopper.y = 200 + Math.random() * (2732 - 400);
shopperGraphics = shopper.children[0];
// Randomly rotate the shopper
shopperGraphics.rotation = Math.random() * Math.PI * 2;
gameArea.addChild(shopper);
shoppers.push(shopper);
// Stagger the movement starts
LK.setTimeout(function (s) {
return function () {
s.startMoving();
};
}(shopper), i * 300);
}
}
function setupEventTimers() {
// Special deals event (every 10-15 seconds)
specialDealTimer = LK.setInterval(function () {
createSpecialDeal();
}, 10000 + Math.random() * 5000);
// Cleanup event (every 15-20 seconds)
cleanupTimer = LK.setInterval(function () {
createCleanupObstacle();
}, 15000 + Math.random() * 5000);
// Demon spawner event (every 20-30 seconds)
demonTimer = LK.setInterval(function () {
spawnDemon();
}, 20000 + Math.random() * 10000);
// Friend spawner event (every 15-25 seconds)
friendTimer = LK.setInterval(function () {
spawnFriend();
}, 15000 + Math.random() * 10000);
}
function createSpecialDeal() {
// Pick a random item to make a special deal
if (items.length > 0) {
var randomIndex = Math.floor(Math.random() * items.length);
var item = items[randomIndex];
if (!item.isSpecial && !item.collected) {
item.makeSpecialDeal();
// Show flash sale message
showMessage("FLASH SALE!", 2000);
}
}
}
function createCleanupObstacle() {
// Create cleanup on a random aisle
var randomAisleIndex = Math.floor(Math.random() * aisles.length);
var aisle = aisles[randomAisleIndex];
var obstacle = new Obstacle();
obstacle.x = aisle.x;
obstacle.y = 500 + Math.random() * 1500;
gameArea.addChild(obstacle);
obstacles.push(obstacle);
// Show cleanup message
showMessage("CLEANUP ON AISLE " + (randomAisleIndex + 1), 2000);
}
function updateShoppingListText() {
var listText = "Shopping List:\n";
var remainingItems = 0;
for (var i = 0; i < shoppingList.length; i++) {
var collected = false;
// Check if item has been collected
if (cart && cart.collectedItems) {
collected = cart.collectedItems.indexOf(shoppingList[i].name) !== -1;
}
if (!collected) {
remainingItems++;
listText += "⢠" + shoppingList[i].name + " - $" + shoppingList[i].price.toFixed(2) + "\n";
} else {
listText += "ā " + shoppingList[i].name + " - $" + shoppingList[i].price.toFixed(2) + "\n";
}
}
if (shoppingListText) {
shoppingListText.setText(listText);
}
return remainingItems;
}
function showMessage(text, duration) {
if (messageText) {
messageText.setText(text);
messageText.visible = true;
LK.setTimeout(function () {
messageText.visible = false;
}, duration);
}
}
function createUI() {
// Create budget display
budgetText = new Text2("Budget: $0.00", {
size: 40,
fill: 0x008000
});
budgetText.anchor.set(0, 0);
LK.gui.topRight.addChild(budgetText);
// Create timer display
timerText = new Text2("Time: 0:00", {
size: 40,
fill: 0xDE2027
});
timerText.anchor.set(0.5, 0);
LK.gui.top.addChild(timerText);
// Create lives display
livesText = new Text2("Lives: 3", {
size: 40,
fill: 0xFF6600
});
livesText.anchor.set(0.5, 0);
livesText.y = 50; // Position below timer
LK.gui.top.addChild(livesText);
// Create level display
levelText = new Text2("Level: 1", {
size: 40,
fill: 0x0071CE
});
levelText.anchor.set(1, 0);
LK.gui.topLeft.addChild(levelText);
// Move it a bit to the right to avoid the platform menu
levelText.x += 120;
// Create shopping list display
shoppingListText = new Text2("Shopping List:", {
size: 30,
fill: 0x000000
});
shoppingListText.anchor.set(1, 0);
shoppingListText.x = -20; // Move it a bit to the left to ensure visibility
LK.gui.right.addChild(shoppingListText);
// Create message text for events
messageText = new Text2("", {
size: 60,
fill: 0xFF0000
});
messageText.anchor.set(0.5, 0.5);
messageText.visible = false;
LK.gui.center.addChild(messageText);
// Create minimap in bottom left corner
miniMap = new MiniMap();
miniMap.x = 20;
miniMap.y = -250; // Position from bottom
LK.gui.bottomLeft.addChild(miniMap);
}
function updateUI() {
if (!cart) {
return;
}
// Update budget display
budgetText.setText("Budget: $" + cart.budget.toFixed(2));
// Update timer display
var minutes = Math.floor(timeRemaining / 60);
var seconds = timeRemaining % 60;
timerText.setText("Time: " + minutes + ":" + (seconds < 10 ? "0" : "") + seconds);
// Update lives display
livesText.setText("Lives: " + lives);
// Update level display
levelText.setText("Level: " + level);
// Update shopping list
var remainingItems = updateShoppingListText();
// If no items remaining, highlight cashier to indicate checkout
if (remainingItems === 0) {
// Find cashier and set indicator
for (var c = 0; c < gameArea.children.length; c++) {
if (gameArea.children[c] instanceof Cashier) {
gameArea.children[c].showCheckoutReady();
break;
}
}
// No longer win automatically - need to go to cashier
}
}
// Handle touch/drag controls
var dragging = false;
function handleMove(x, y, obj) {
if (dragging && cart) {
// Move the cart to the touch position
cart.x = x;
cart.y = y;
// Keep within game boundaries
cart.x = Math.max(cart.width / 2, Math.min(2048 - cart.width / 2, cart.x));
cart.y = Math.max(cart.height / 2, Math.min(2732 - cart.height / 2, cart.y));
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
if (showingMainMenu) {
// Let the menu handle its own click events
return;
}
if (!gameStarted) {
initLevel();
return;
}
if (cart && Math.abs(x - cart.x) < cart.width / 2 && Math.abs(y - cart.y) < cart.height / 2) {
dragging = true;
}
handleMove(x, y, obj);
};
game.up = function (x, y, obj) {
dragging = false;
};
// Main game loop
var lastSecondTick = 0;
game.update = function () {
if (showingMainMenu) {
// Don't update game while showing menu
return;
}
if (!gameStarted) {
return;
}
// Update timer once per second
if (LK.ticks % 60 === 0) {
timeRemaining--;
updateUI();
// Check if time is up
if (timeRemaining <= 0) {
gameStarted = false;
showMessage("TIME'S UP!", 2000);
LK.setTimeout(function () {
LK.showGameOver();
}, 2000);
}
}
// Update power-up indicators every 16 frames (roughly 4 times per second)
if (cart && LK.ticks % 15 === 0) {
cart.updatePowerUpIndicator();
// Update minimap
if (miniMap) {
miniMap.update(cart, shoppers, items, obstacles, aisles);
}
}
// Apply magnet effect if active
if (cart && cart.powerUps.magnet && cart.powerUps.magnet.active && cart.magnetRange > 0) {
for (var i = 0; i < items.length; i++) {
var item = items[i];
if (!item.collected && !(item instanceof PowerUp)) {
var dx = cart.x - item.x;
var dy = cart.y - item.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < cart.magnetRange) {
// Move item toward cart
var speed = 5 + (1 - distance / cart.magnetRange) * 10;
item.x += dx / distance * speed;
item.y += dy / distance * speed;
}
}
}
}
// Update all shoppers
for (var i = 0; i < shoppers.length; i++) {
shoppers[i].update();
}
// Update all obstacles
for (var j = obstacles.length - 1; j >= 0; j--) {
obstacles[j].update();
if (obstacles[j].readyToRemove) {
gameArea.removeChild(obstacles[j]);
obstacles.splice(j, 1);
}
}
// Check for collisions with cart
if (cart) {
// Check for collisions with items
for (var k = 0; k < items.length; k++) {
var item = items[k];
if (!item.collected && cart.intersects(item)) {
// Check if item is a power-up
if (item instanceof PowerUp) {
// Collect power-up
item.collect();
LK.getSound('pickup').play();
LK.setScore(LK.getScore() + 15);
// Apply the power-up effect
cart.applyPowerUp(item.type, item.duration);
showMessage(item.type.toUpperCase() + " POWER-UP!", 1000);
continue;
}
if (item.onList) {
// Collect item on shopping list
if (cart.addItem(item)) {
item.collect();
LK.getSound('pickup').play();
LK.setScore(LK.getScore() + 10);
updateUI();
} else {
// Not enough budget
if (!cart.isColliding) {
showMessage("Not enough budget!", 1000);
cart.isColliding = true;
LK.setTimeout(function () {
cart.isColliding = false;
}, 1000);
}
}
} else if (item.isSpecial) {
// Collect special item
if (cart.addItem(item)) {
item.collect();
LK.getSound('deal').play();
LK.setScore(LK.getScore() + 5);
updateUI();
} else {
// Not enough budget
if (!cart.isColliding) {
showMessage("Not enough budget!", 1000);
cart.isColliding = true;
LK.setTimeout(function () {
cart.isColliding = false;
}, 1000);
}
}
}
}
}
// Check for collisions with shoppers
for (var m = 0; m < shoppers.length; m++) {
if (cart.intersects(shoppers[m]) && !cart.isColliding) {
// Check if shield power-up is active
if (cart.powerUps.shield && cart.powerUps.shield.active) {
// Shield protects from collision
LK.getSound('collision').play();
LK.effects.flashObject(cart, 0x0000FF, 500); // Blue flash for shield
cart.isColliding = true;
showMessage("SHIELD PROTECTED YOU!", 1000);
// Move shopper away
var dx = shoppers[m].x - cart.x;
var dy = shoppers[m].y - cart.y;
var distance = Math.sqrt(dx * dx + dy * dy);
shoppers[m].x += dx / distance * 150;
shoppers[m].y += dy / distance * 150;
LK.setTimeout(function () {
cart.isColliding = false;
}, 1000);
} else {
// Collision with shopper - Game Over!
LK.getSound('collision').play();
LK.effects.flashObject(cart, 0xFF0000, 500);
cart.isColliding = true;
// Show death message and end game
showMessage("GAME OVER! Other shopper crashed into you!", 2000);
gameStarted = false;
updateUI();
LK.setTimeout(function () {
LK.showGameOver();
// Show main menu after game over
LK.setTimeout(function () {
mainMenu.visible = true;
showingMainMenu = true;
}, 1000);
}, 2000);
}
}
}
// Check for collisions with obstacles
for (var n = 0; n < obstacles.length; n++) {
if (cart.intersects(obstacles[n]) && !cart.isColliding) {
// Collision with obstacle
LK.getSound('collision').play();
LK.effects.flashObject(cart, 0xFF0000, 500);
cart.isColliding = true;
// Lose a life instead of time penalty
lives--;
updateUI();
// Check if out of lives
if (lives <= 0) {
// Game over when no lives left
showMessage("OUT OF LIVES! GAME OVER!", 2000);
gameStarted = false;
LK.setTimeout(function () {
LK.showGameOver();
}, 2000);
} else {
// Show message about losing a life
showMessage("OUCH! " + lives + " LIVES LEFT!", 1000);
LK.setTimeout(function () {
cart.isColliding = false;
}, 1000);
}
}
}
// Update and check collision with demon from initial level
if (game.demon && game.demon.active) {
// Update demon
game.demon.update();
// Check if demon collided with cashier
for (var c = 0; c < gameArea.children.length; c++) {
if (gameArea.children[c] instanceof Cashier && game.demon.intersects(gameArea.children[c]) && game.demon.active) {
// Demon reached cashier - Game Over!
LK.getSound('collision').play();
LK.effects.flashScreen(0xFF0000, 1000);
showMessage("GAME OVER! Demon reached the cashier!", 2000);
gameStarted = false;
LK.setTimeout(function () {
LK.showGameOver();
}, 2000);
return;
}
}
// Check if player touched the demon
if (cart && cart.intersects(game.demon) && !cart.isColliding) {
// Player touched demon - defeat it!
game.demon.hit();
game.demon.active = false;
LK.getSound('pickup').play();
LK.setScore(LK.getScore() + 50);
showMessage("DEMON DEFEATED! +50 POINTS", 2000);
// Make demon disappear slowly
tween(game.demon, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
gameArea.removeChild(game.demon);
}
});
}
}
// Update and check collision with spawned demons
for (var d = demons.length - 1; d >= 0; d--) {
var currentDemon = demons[d];
// Update demon
if (currentDemon.active) {
currentDemon.update();
// Check if demon collided with cashier
for (var c = 0; c < gameArea.children.length; c++) {
if (gameArea.children[c] instanceof Cashier && currentDemon.intersects(gameArea.children[c]) && currentDemon.active) {
// Demon reached cashier - Game Over!
LK.getSound('collision').play();
LK.effects.flashScreen(0xFF0000, 1000);
showMessage("GAME OVER! Demon reached the cashier!", 2000);
gameStarted = false;
LK.setTimeout(function () {
LK.showGameOver();
}, 2000);
return;
}
}
// Check if player touched the demon
if (cart && cart.intersects(currentDemon) && !cart.isColliding) {
// Player touched demon - defeat it!
currentDemon.hit();
currentDemon.active = false;
LK.getSound('pickup').play();
LK.setScore(LK.getScore() + 50);
showMessage("DEMON DEFEATED! +50 POINTS", 2000);
// Make demon disappear slowly
tween(currentDemon, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function (demonIndex) {
return function () {
if (demons[demonIndex]) {
gameArea.removeChild(demons[demonIndex]);
demons.splice(demonIndex, 1);
}
};
}(d)
});
// Set cart as colliding briefly to prevent multiple demon hits
cart.isColliding = true;
LK.setTimeout(function () {
cart.isColliding = false;
}, 500);
}
}
}
// Check for collisions with walls in Karen mode
for (var w = 0; w < obstacles.length; w++) {
if (obstacles[w] instanceof Wall && cart.intersects(obstacles[w]) && !cart.isColliding) {
// Collision with wall
LK.getSound('collision').play();
LK.effects.flashObject(cart, 0xFF0000, 500);
cart.isColliding = true;
// Lose a life for hitting a wall
lives--;
updateUI();
// Check if out of lives
if (lives <= 0) {
// Game over when no lives left
showMessage("OUT OF LIVES! GAME OVER!", 2000);
gameStarted = false;
LK.setTimeout(function () {
LK.showGameOver();
}, 2000);
} else {
// Show message about losing a life
showMessage("HIT A WALL! " + lives + " LIVES LEFT!", 1000);
LK.setTimeout(function () {
cart.isColliding = false;
}, 1000);
}
}
}
// Update and check collision with obstacle remover
for (var r = 0; r < gameArea.children.length; r++) {
if (gameArea.children[r] instanceof ObstacleRemover && cart.intersects(gameArea.children[r])) {
var remover = gameArea.children[r];
if (remover.active) {
// Found the remover! Use it to remove obstacles
remover.active = false;
// Visual effect for remover pickup
LK.getSound('pickup').play();
LK.effects.flashObject(remover, 0x00FF00, 500);
// Show message
showMessage("OBSTACLE REMOVER FOUND! Cleaning up...", 2000);
// Remove all obstacles with animation
for (var o = 0; o < obstacles.length; o++) {
(function (obstacle, index) {
tween(obstacle, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
// Bonus points for each obstacle removed
LK.setScore(LK.getScore() + 10);
}
});
})(obstacles[o], o);
}
// Clear obstacles array after animation
LK.setTimeout(function () {
while (obstacles.length > 0) {
if (obstacles[0].parent) {
gameArea.removeChild(obstacles[0]);
}
obstacles.splice(0, 1);
}
}, 1100);
// Remove the remover with animation
tween(remover, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
gameArea.removeChild(remover);
}
});
}
}
// Update any obstacle removers (for animation)
if (gameArea.children[r] instanceof ObstacleRemover) {
gameArea.children[r].update();
}
}
// Check for collision with friends
if (friends && friends.length > 0) {
for (var f = 0; f < friends.length; f++) {
if (cart && cart.intersects(friends[f]) && !cart.isColliding && !friends[f].isInteracting) {
// Start conversation with friend
friends[f].startConversation();
cart.isColliding = true;
// Set timeout to reset collision status after conversation
LK.setTimeout(function () {
cart.isColliding = false;
}, 1000);
}
}
}
// Check for collision with cashier for level completion
for (var c = 0; c < gameArea.children.length; c++) {
if (gameArea.children[c] instanceof Cashier && cart.intersects(gameArea.children[c])) {
// Check if all items have been collected
var remainingItems = updateShoppingListText();
if (remainingItems === 0 && !cart.isColliding) {
// Level complete!
level++;
showMessage("CHECKOUT COMPLETE! LEVEL UP!", 3000);
LK.setTimeout(initLevel, 3000);
gameStarted = false;
// Award bonus points for remaining time
var timeBonus = timeRemaining * 2;
LK.setScore(LK.getScore() + timeBonus);
break;
} else if (remainingItems > 0 && !cart.isColliding) {
// Still have items to collect
showMessage("Finish your shopping first!", 1000);
cart.isColliding = true;
LK.setTimeout(function () {
cart.isColliding = false;
}, 1000);
}
}
}
}
};
// Initialize UI
createUI();
// Don't show start instructions since we now have a menu
function createPowerUps() {
// Create 3 power-ups of different types, randomly placed in aisles
var powerUpTypes = ['speed', 'magnet', 'shield'];
for (var i = 0; i < powerUpTypes.length; i++) {
var powerUp = new PowerUp();
var aisleIndex = Math.floor(Math.random() * aisles.length);
var aisle = aisles[aisleIndex];
powerUp.x = aisle.x + (Math.random() * 200 - 100);
powerUp.y = 200 + Math.random() * (2732 - 400);
// Make sure power-up doesn't spawn inside a wall
var validPosition = false;
var attempts = 0;
while (!validPosition && attempts < 10) {
validPosition = true;
// Check if power-up would intersect with any obstacle
for (var w = 0; w < obstacles.length; w++) {
if (Math.abs(powerUp.x - obstacles[w].x) < 100 && Math.abs(powerUp.y - obstacles[w].y) < 100) {
validPosition = false;
// Try a different position
powerUp.x = aisle.x + (Math.random() * 200 - 100);
powerUp.y = 200 + Math.random() * (2732 - 400);
break;
}
}
attempts++;
}
powerUp.setType(powerUpTypes[i]);
gameArea.addChild(powerUp);
items.push(powerUp); // Add to items array for collision detection
}
}
function spawnFriend() {
if (!gameStarted) {
return;
}
// Create a new friend
var newFriend = new Friend();
// Place friend at a random location in the store
var aisleIndex = Math.floor(Math.random() * aisles.length);
var aisle = aisles[aisleIndex];
newFriend.x = aisle.x + (Math.random() * 200 - 100);
newFriend.y = 200 + Math.random() * (2732 - 400);
// Initialize tracking properties
newFriend.lastX = newFriend.x;
newFriend.lastY = newFriend.y;
// Add to game
gameArea.addChild(newFriend);
// Add to a friends array
if (!friends) {
friends = [];
}
friends.push(newFriend);
// Show message
showMessage("A FRIEND IS NEARBY! GO SAY HI!", 2000);
}
function spawnDemon() {
if (!gameStarted) {
return;
}
// Create a new demon
var newDemon = new ShoppingDemon();
// Place demon at a random edge of the screen
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
newDemon.x = Math.random() * 2048;
newDemon.y = 50;
break;
case 1:
// Right
newDemon.x = 2048 - 50;
newDemon.y = Math.random() * 2732;
break;
case 2:
// Bottom
newDemon.x = Math.random() * 2048;
newDemon.y = 2732 - 50;
break;
case 3:
// Left
newDemon.x = 50;
newDemon.y = Math.random() * 2732;
break;
}
// Find the cashier to target
for (var c = 0; c < gameArea.children.length; c++) {
if (gameArea.children[c] instanceof Cashier) {
newDemon.setTarget(gameArea.children[c]);
break;
}
}
// Initialize tracking properties
newDemon.lastX = newDemon.x;
newDemon.lastY = newDemon.y;
// Add to game
gameArea.addChild(newDemon);
demons.push(newDemon);
// Show warning message
showMessage("SHOPPING DEMON APPROACHING!", 2000);
}
Top-down image of a shopping cart. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Top-down image of a person. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Sale! Sign. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Water spill. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Box of food. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Shield. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Cart with letters saying WALMART DASH!. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows