/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { petLevel: 1, petXp: 0, lastFed: 0, lastCleaned: 0, lastPlayed: 0, petName: "undefined", petType: "Fluffy" }); /**** * Classes ****/ var ActionButton = Container.expand(function (actionType, position) { var self = Container.call(this); var buttonShape; if (actionType === 'feed') { buttonShape = self.attachAsset('foodItem', { anchorX: 0.5, anchorY: 0.5 }); } else if (actionType === 'clean') { buttonShape = self.attachAsset('cleaningItem', { anchorX: 0.5, anchorY: 0.5 }); } else if (actionType === 'play') { buttonShape = self.attachAsset('playItem', { anchorX: 0.5, anchorY: 0.5 }); } self.buttonType = actionType; self.interactive = true; self.down = function (x, y, obj) { tween(buttonShape, { scaleX: 0.8, scaleY: 0.8 }, { duration: 100 }); }; self.up = function (x, y, obj) { tween(buttonShape, { scaleX: 1, scaleY: 1 }, { duration: 100, onFinish: function onFinish() { if (self.buttonType === 'feed') { performFeedAction(); } else if (self.buttonType === 'clean') { performCleanAction(); } else if (self.buttonType === 'play') { performPlayAction(); } } }); }; return self; }); var BubbleSpeech = Container.expand(function (text) { var self = Container.call(this); // Create the bubble background var bubbleBackground = LK.getAsset('bubble_speech', { anchorX: 0.5, anchorY: 0.5, width: 600, height: 600, tint: 0xFFFFFF }); // Round the corners by scaling bubbleBackground.scale.set(1); self.addChild(bubbleBackground); // Add text to bubble var bubbleText = new Text2(text || "", { size: 80, fill: 0x000000, align: "center", // You can set alignment to "left", "center", or "right" wordWrap: true, wordWrapWidth: 800 // Set this to control where text will wra }); bubbleText.anchor.set(0.5, 0.5); self.addChild(bubbleText); // Public methods self.setText = function (newText) { bubbleText.setText(newText); }; // Show with animation self.show = function (duration) { self.alpha = 0; self.scale.set(0.5); tween(self, { alpha: 1, scaleX: 1, scaleY: 1 }, { duration: duration || 300, easing: tween.easeOutBack }); }; // Hide with animation self.hide = function (duration, callback) { tween(self, { alpha: 0, scaleX: 0.5, scaleY: 0.5 }, { duration: duration || 300, easing: tween.easeInBack, onFinish: function onFinish() { if (callback) { callback(); } } }); }; return self; }); var CharacterSelect = Container.expand(function () { var self = Container.call(this); // Background image var background = self.attachAsset('menu_background', { anchorX: 0.5, anchorY: 0.5 }); background.x = 2048 / 2; background.y = 2732 / 2; // Title text var titleText = new Text2("Choose Your Pet", { size: 100, fill: 0xFFFFFF }); titleText.anchor.set(0.5, 0.5); titleText.x = 2048 / 2; titleText.y = 500; self.addChild(titleText); // Create character options var character1 = self.attachAsset('pet_select_1', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 3, y: 2732 / 2 }); character1.interactive = true; var character2 = self.attachAsset('pet_select_2', { anchorX: 0.5, anchorY: 0.5, x: 2048 * 2 / 3, y: 2732 / 2 }); character2.interactive = true; // Character 1 label var label1 = new Text2("Fluffy", { size: 70, fill: 0xFFFFFF }); label1.anchor.set(0.5, 0.5); label1.x = character1.x; label1.y = character1.y + 350; self.addChild(label1); // Character 2 label var label2 = new Text2("Puffy", { size: 70, fill: 0xFFFFFF }); label2.anchor.set(0.5, 0.5); label2.x = character2.x; label2.y = character2.y + 350; self.addChild(label2); // Button animations and interaction character1.down = function (x, y, obj) { tween(character1, { scaleX: 0.95, scaleY: 0.95 }, { duration: 100 }); }; character1.up = function (x, y, obj) { tween(character1, { scaleX: 1, scaleY: 1 }, { duration: 100, onFinish: function onFinish() { if (self.onSelectCharacter) { storage.petType = 'Fluffy'; storage.petXp = 0; storage.petLevel = 1; self.onSelectCharacter('Fluffy'); } } }); }; character2.down = function (x, y, obj) { tween(character2, { scaleX: 0.95, scaleY: 0.95 }, { duration: 100 }); }; character2.up = function (x, y, obj) { tween(character2, { scaleX: 1, scaleY: 1 }, { duration: 100, onFinish: function onFinish() { if (self.onSelectCharacter) { storage.petType = 'Puffy'; storage.petXp = 0; storage.petLevel = 1; self.onSelectCharacter('Puffy'); } } }); }; return self; }); var MainMenu = Container.expand(function () { var self = Container.call(this); // Background image var background = self.attachAsset('menu_background', { anchorX: 0.5, anchorY: 0.5 }); background.x = 2048 / 2; background.y = 2732 / 2; // Game logo var gameLogo = self.attachAsset('game_logo', { anchorX: 0.5, anchorY: 0.5 }); gameLogo.x = 2048 / 2; gameLogo.y = 800; // Play button image var playButton = self.attachAsset('play_button', { anchorX: 0.5, anchorY: 0.5 }); playButton.x = 2048 / 2; playButton.y = 2732 / 2 + 200; playButton.interactive = true; // Reset button var resetButton = new Text2("Reset Game", { size: 60, fill: 0xFFFFFF }); resetButton.anchor.set(0.5, 0.5); resetButton.x = 2048 / 2; resetButton.y = 2732 / 2 + 400; resetButton.interactive = true; self.addChild(resetButton); // Button animations and interaction playButton.down = function (x, y, obj) { tween(playButton, { scaleX: 0.95, scaleY: 0.95 }, { duration: 100 }); }; playButton.up = function (x, y, obj) { tween(playButton, { scaleX: 1, scaleY: 1 }, { duration: 100, onFinish: function onFinish() { if (self.onStartGame) { self.onStartGame(); } } }); }; // Reset button interactions resetButton.down = function (x, y, obj) { tween(resetButton, { scaleX: 0.95, scaleY: 0.95 }, { duration: 100 }); }; resetButton.up = function (x, y, obj) { tween(resetButton, { scaleX: 1, scaleY: 1 }, { duration: 100, onFinish: function onFinish() { if (self.onResetGame) { self.onResetGame(); } } }); }; // Add some animation to the logo function animateLogo() { tween(gameLogo, { y: gameLogo.y - 15 }, { duration: 1500, easing: tween.easeInOut, onFinish: function onFinish() { tween(gameLogo, { y: gameLogo.y + 15 }, { duration: 1500, easing: tween.easeInOut, onFinish: animateLogo }); } }); } // Start the animation animateLogo(); return self; }); var MemoryCard = Container.expand(function (cardValue, cardBack) { var self = Container.call(this); self.value = cardValue; // Card match value self.revealed = false; // Is card face up self.matched = false; // Is card matched // Create card front (colored shape) self.front = LK.getAsset('memory_card_' + cardValue, { anchorX: 0.5, anchorY: 0.5 }); self.front.visible = false; self.addChild(self.front); // Create card back self.back = LK.getAsset('memory_card_back', { anchorX: 0.5, anchorY: 0.5 }); self.addChild(self.back); // Make card interactive self.interactive = true; // Flip card to show front self.reveal = function () { if (self.matched || self.revealed) { return; } // Already revealed or matched self.revealed = true; // Animate card flip tween(self.scale, { x: 0 }, { duration: 150, easing: tween.easeInQuad, onFinish: function onFinish() { self.front.visible = true; self.back.visible = false; tween(self.scale, { x: 1 }, { duration: 150, easing: tween.easeOutQuad }); } }); if (self.onReveal) { self.onReveal(self); } }; // Flip card to hide front self.hide = function () { if (self.matched) { return; } // Don't hide if matched // Animate card flip tween(self.scale, { x: 0 }, { duration: 150, easing: tween.easeInQuad, onFinish: function onFinish() { self.front.visible = false; self.back.visible = true; self.revealed = false; tween(self.scale, { x: 1 }, { duration: 150, easing: tween.easeOutQuad }); } }); }; // Mark card as matched self.setMatched = function () { self.matched = true; self.interactive = false; // Flash effect for matching tween(self, { alpha: 0.5 }, { duration: 200, easing: tween.easeInQuad, onFinish: function onFinish() { tween(self, { alpha: 1 }, { duration: 200, easing: tween.easeOutQuad }); } }); }; // Handle card click self.down = function (x, y, obj) { // If card is already matched, or not interactive, do nothing. if (self.matched || !self.interactive) { return; } // If card is already revealed (face up), do nothing on 'down'. if (self.revealed) { return; } // If two cards are already face up (checked via currentMemoryGame.canSelectCard()), // do not allow this card (which is currently face down) to be pressed/animated. // currentMemoryGame might be null if the game is not active, so check it. if (currentMemoryGame && !currentMemoryGame.canSelectCard()) { return; } // Card is face down, interactive, not matched, and we are allowed to select a card. // Animate press down. tween(self, { scaleX: 0.95, scaleY: 0.95 }, { duration: 100 }); }; self.up = function (x, y, obj) { // If card is already matched, or not interactive, do nothing. if (self.matched || !self.interactive) { return; } // If card is already revealed (face up), it means it was one of the selected cards. // Clicking it again on 'up' should just restore its scale if it was pressed. No re-reveal. if (self.revealed) { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 100 }); // Restore scale return; } // At this point, card is face down, interactive, and not matched. // We need to check again if we are allowed to reveal it. This is a final gate. // The 'down' action might have scaled it down. // If currentMemoryGame is null or canSelectCard is false, just restore scale and don't reveal. if (!currentMemoryGame || !currentMemoryGame.canSelectCard()) { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 100 }); // Restore scale return; } // Allowed to reveal. Animate scale back and then reveal. tween(self, { scaleX: 1, scaleY: 1 }, { duration: 100, onFinish: function onFinish() { self.reveal(); // self.reveal() has its own checks too. } }); }; return self; }); var MemoryGame = Container.expand(function () { var self = Container.call(this); // Game state variables var selectedCards = []; var matchedPairs = 0; var totalPairs = 8; var canSelect = true; var movesMade = 0; // Create cards array var cards = []; // Create semi-transparent background var gameBackground = LK.getAsset('pet_playing', { anchorX: 0.5, anchorY: 0.5, width: 2000, height: 2000, tint: 0x000000 }); gameBackground.alpha = 0.7; self.addChild(gameBackground); // Title text var titleText = new Text2("Memory Game", { size: 80, fill: 0xFFFFFF }); titleText.anchor.set(0.5, 0.5); titleText.y = -900; self.addChild(titleText); // Moves counter var movesText = new Text2("Moves: 0", { size: 60, fill: 0xFFFFFF }); movesText.anchor.set(0.5, 0.5); movesText.y = -800; self.addChild(movesText); // Setup the game self.startGame = function () { currentMemoryGame = self; // Set the global reference // Reset game state selectedCards = []; matchedPairs = 0; movesMade = 0; canSelect = true; // Create card values (pairs of 1-8) var values = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8]; // Shuffle the array for (var i = values.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var temp = values[i]; values[i] = values[j]; values[j] = temp; } // Create and position cards in a 4x4 grid var gridSize = 4; // Create a temp card to measure actual size var tempCard = new MemoryCard(1); // Calculate spacing based on actual card dimensions var cardWidth = tempCard.back.width; var cardHeight = tempCard.back.height; var cardSpacing = cardWidth * 1.2; // Add 20% of width as spacing var verticalSpacing = cardHeight * 1.4; // Add 40% of height as vertical spacing // Calculate starting coordinates using actual card dimensions var startX = -((gridSize - 1) * cardSpacing) / 2; var startY = -((gridSize - 1) * verticalSpacing) / 2; // Clean up temp card tempCard = null; for (var row = 0; row < gridSize; row++) { for (var col = 0; col < gridSize; col++) { var index = row * gridSize + col; var card = new MemoryCard(values[index]); // Position card in grid using dynamic spacing based on card size card.x = startX + col * cardSpacing; card.y = startY + row * verticalSpacing; // Set up reveal callback card.onReveal = handleCardReveal; // Add to game self.addChild(card); cards.push(card); } } // Update moves display updateMovesText(); // Fade in animation self.alpha = 0; tween(self, { alpha: 1 }, { duration: 500 }); }; // Handle card reveal function handleCardReveal(card) { if (!canSelect) { return; } // Add card to selected cards selectedCards.push(card); // If we have 2 cards selected if (selectedCards.length === 2) { canSelect = false; movesMade++; updateMovesText(); // Check for match if (selectedCards[0].value === selectedCards[1].value) { // Match found! selectedCards[0].setMatched(); selectedCards[1].setMatched(); matchedPairs++; // Clear selection selectedCards = []; canSelect = true; // Check if game is complete if (matchedPairs === totalPairs) { // Game won! LK.setTimeout(function () { endGame(); }, 500); } } else { // No match, flip cards back after a delay LK.setTimeout(function () { selectedCards[0].hide(); selectedCards[1].hide(); selectedCards = []; canSelect = true; }, 1000); } } } // Update moves text function updateMovesText() { movesText.setText("Moves: " + movesMade); } self.canSelectCard = function () { return canSelect; }; // End the game function endGame() { // Calculate score (higher for fewer moves) var baseScore = 100; var movesPenalty = Math.min(80, movesMade * 2); var finalScore = Math.max(20, baseScore - movesPenalty); // Show winning message var winText = new Text2("Memory Game Complete!\nScore: " + finalScore, { size: 80, fill: 0xFFFFFF, align: "center" }); winText.anchor.set(0.5, 0.5); self.addChild(winText); // Animate text winText.scale.set(0); tween(winText.scale, { x: 1, y: 1 }, { duration: 500, easing: tween.elasticOut }); // Close after delay LK.setTimeout(function () { // Fade out animation tween(self, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { if (self.onComplete) { self.onComplete(finalScore); } currentMemoryGame = null; // Clear the global reference // Remove all cards for (var i = 0; i < cards.length; i++) { self.removeChild(cards[i]); } cards = []; // Remove win text self.removeChild(winText); // Remove self from parent if (self.parent) { self.parent.removeChild(self); } } }); }, 2000); } // Close button var closeButton = new Text2("X", { size: 80, fill: 0xFFFFFF }); closeButton.anchor.set(0.5, 0.5); closeButton.x = 900; closeButton.y = -900; closeButton.interactive = true; self.addChild(closeButton); closeButton.down = function () { tween(closeButton, { scaleX: 0.9, scaleY: 0.9 }, { duration: 100 }); }; closeButton.up = function () { tween(closeButton, { scaleX: 1, scaleY: 1 }, { duration: 100, onFinish: function onFinish() { // Fade out and close tween(self, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { // Calculate partial score var partialScore = Math.max(5, matchedPairs * 10); if (self.onComplete) { self.onComplete(partialScore); } currentMemoryGame = null; // Clear the global reference // Remove all cards for (var i = 0; i < cards.length; i++) { self.removeChild(cards[i]); } cards = []; // Remove self from parent if (self.parent) { self.parent.removeChild(self); } } }); } }); }; // Center the game on screen self.x = 2048 / 2; self.y = 2732 / 2; // Start the game self.startGame(); return self; }); var NameSelect = Container.expand(function (petType) { var self = Container.call(this); var petName = ""; self.petType = petType; // Background setup var background = self.attachAsset('menu_background', { anchorX: 0.5, anchorY: 0.5 }); background.x = 2048 / 2; background.y = 2732 / 2; // Title text var titleText = new Text2("Name Your Pet", { size: 100, fill: 0xFFFFFF }); titleText.anchor.set(0.5, 0.5); titleText.x = 2048 / 2; titleText.y = 500; self.addChild(titleText); // Add selected pet preview var petPreview = self.attachAsset(petType === 'Fluffy' ? 'pet_select_1' : 'pet_select_2', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 - 300 }); // Name input display var nameDisplay = new Text2("_", { size: 80, fill: 0xFFFFFF }); nameDisplay.anchor.set(0.5, 0.5); nameDisplay.x = 2048 / 2; nameDisplay.y = 2732 / 2 + 100; self.addChild(nameDisplay); // Instruction text var instructionText = new Text2("Tap letters to name your pet", { size: 60, fill: 0xFFFFFF }); instructionText.anchor.set(0.5, 0.5); instructionText.x = 2048 / 2; instructionText.y = 2732 / 2 + 200; self.addChild(instructionText); // Create keyboard layout var keyboard = new Container(); keyboard.x = 2048 / 2; keyboard.y = 2732 / 2 + 400; self.addChild(keyboard); // Define keyboard characters var keys = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "⌫"]; // Create keyboard buttons var keySize = 150; var keysPerRow = 7; var keySpacing = 15; for (var i = 0; i < keys.length; i++) { var row = Math.floor(i / keysPerRow); var col = i % keysPerRow; // Create key background var keyBg = LK.getAsset('progressBar_bg', { anchorX: 0.5, anchorY: 0.5, width: keySize, height: keySize, tint: keys[i] === "⌫" ? 0xFF6B6B : 0x70a1ff }); // Create key text var keyText = new Text2(keys[i], { size: 60, fill: 0xFFFFFF }); keyText.anchor.set(0.5, 0.5); // Create key container var key = new Container(); key.addChild(keyBg); key.addChild(keyText); key.x = (col - keysPerRow / 2) * (keySize + keySpacing); key.y = row * (keySize + keySpacing); key.keyValue = keys[i]; key.interactive = true; // Key interactions key.down = function (x, y, obj) { tween(this, { scaleX: 0.9, scaleY: 0.9 }, { duration: 100 }); }; key.up = function (x, y, obj) { var keyValue = this.keyValue; tween(this, { scaleX: 1, scaleY: 1 }, { duration: 100, onFinish: function onFinish() { // Handle backspace if (keyValue === "⌫") { if (petName.length > 0) { petName = petName.slice(0, -1); } } // Add letter if name is less than 10 characters else if (petName.length < 10) { petName += keyValue; } // Update name display nameDisplay.setText(petName + "_"); } }); }; keyboard.addChild(key); } // Done button var doneButton = new Container(); var doneBg = LK.getAsset('progressBar_bg', { anchorX: 0.5, anchorY: 0.5, width: 300, height: 100, tint: 0x7bed9f }); var doneText = new Text2("DONE", { size: 70, fill: 0xFFFFFF }); doneText.anchor.set(0.5, 0.5); doneButton.addChild(doneBg); doneButton.addChild(doneText); doneButton.x = 2048 / 2; doneButton.y = 2732 / 2 + 1200; doneButton.interactive = true; self.addChild(doneButton); doneButton.down = function (x, y, obj) { tween(doneButton, { scaleX: 0.95, scaleY: 0.95 }, { duration: 100 }); }; doneButton.up = function (x, y, obj) { tween(doneButton, { scaleX: 1, scaleY: 1 }, { duration: 100, onFinish: function onFinish() { // Use default name if empty if (petName.length === 0) { petName = self.petType; } if (self.onNameSelected) { self.onNameSelected(petName); } } }); }; return self; }); var Pet = Container.expand(function () { var self = Container.call(this); var currentLevel = storage.petLevel || 1; var petName = storage.petName || "Pet"; var petType = storage.petType || "Fluffy"; var petAssetId = 'pet_baby'; // Determine which asset to use based on level and pet type if (petType === "Puffy") { if (currentLevel === 1) { petAssetId = 'pet_puffy_baby'; } else if (currentLevel === 2) { petAssetId = 'pet_puffy_child'; } else if (currentLevel === 3) { petAssetId = 'pet_puffy_teen'; } else if (currentLevel >= 4) { petAssetId = 'pet_puffy_adult'; } } else { // Default for Fluffy pet if (currentLevel === 1) { petAssetId = 'pet_baby'; } else if (currentLevel === 2) { petAssetId = 'pet_child'; } else if (currentLevel === 3) { petAssetId = 'pet_teen'; } else if (currentLevel >= 4) { petAssetId = 'pet_adult'; } } var petGraphics = self.attachAsset(petAssetId, { anchorX: 0.5, anchorY: 0.5 }); // Add pet name display var nameText = new Text2(petName, { size: 50, fill: 0xFFFFFF }); nameText.anchor.set(0.5, 0.5); nameText.y = -petGraphics.height / 2 - 40; // Initial position above pet self.addChild(nameText); // Method to reposition name to appear above level text self.positionNameAboveLevel = function () { // Remove from pet container self.removeChild(nameText); // Add directly to game container so it can be positioned independently game.addChild(nameText); // Position above level text nameText.x = 2048 / 2; nameText.y = 120; // Position above level text }; self.happy = function () { // Bounce animation tween(self, { y: self.y - 50 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { y: self.y + 50 }, { duration: 300, easing: tween.bounceOut, onFinish: function onFinish() { petPosition.x = self.x; petPosition.y = self.y; } }); } }); }; self.evolve = function () { // Evolution animation LK.getSound('evolve').play(); // Flash and grow LK.effects.flashObject(self, 0xFFFFFF, 1000); tween(petGraphics, { scaleX: 1.5, scaleY: 1.5 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { // Update to new visual based on new level currentLevel = storage.petLevel; var newPetAssetId; var petType = storage.petType || "Fluffy"; if (petType === "Puffy") { if (currentLevel === 2) { newPetAssetId = 'pet_puffy_child'; } else if (currentLevel === 3) { newPetAssetId = 'pet_puffy_teen'; } else if (currentLevel >= 4) { newPetAssetId = 'pet_puffy_adult'; } } else { // Default for Fluffy pet if (currentLevel === 2) { newPetAssetId = 'pet_child'; } else if (currentLevel === 3) { newPetAssetId = 'pet_teen'; } else if (currentLevel >= 4) { newPetAssetId = 'pet_adult'; } } // Remove old graphic and add new one self.removeChild(petGraphics); petGraphics = self.attachAsset(newPetAssetId, { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); // Reset scale with animation tween(petGraphics, { scaleX: 1, scaleY: 1 }, { duration: 300, easing: tween.easeOut }); } }); }; return self; }); var Poop = Container.expand(function () { var self = Container.call(this); // Create poop visual using a single image var poopGraphics = self.attachAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: 1, scaleY: 1, tint: 0x8B4513 // Brown color }); // Add appear animation self.appear = function () { self.scale.set(0); tween(self, { scaleX: 1, scaleY: 1 }, { duration: 300, easing: tween.bounceOut }); }; // Add cleanup animation self.cleanup = function (callback) { tween(self, { alpha: 0, scaleX: 0.5, scaleY: 0.5 }, { duration: 300, easing: tween.easeInBack, onFinish: function onFinish() { if (callback) { callback(); } } }); }; return self; }); var StatusText = Text2.expand(function (initialText) { var self = Text2.call(this, initialText, { size: 60, fill: 0xFFFFFF }); self.anchor.set(0.5, 0.5); self.showMessage = function (message) { self.setText(message); self.alpha = 1; tween(self, { alpha: 0 }, { duration: 3000, easing: tween.linear }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x7ED6DF }); /**** * Game Code ****/ // Game state - load these first var petLevel = storage.petLevel || 1; var petXp = storage.petXp || 0; var lastFed = storage.lastFed || 0; var lastCleaned = storage.lastCleaned || 0; var lastPlayed = storage.lastPlayed || 0; // Load pet state flags from storage var isHungry = false; var isBored = false; var isCheerNeeded = false; var hasActivePoop = false; var canFeedPet = false; var canPlayPet = false; var canCheerPet = false; var canCleanPet = false; var speechBubble; // Variable for the speech bubble // Timers and intervals for needs var needTimer = null; var needTimeout = null; var hungerTimer = null; // Added to fix 'hungerTimer is not defined' var boredTimer = null; // Added to fix 'boredTimer is not defined' var cheerXpLossTimer = null; // Timer for XP loss when cheer is ignored var XP_LOSS_TIMEOUT = 60000; // 1 minute var POOP_SPEECH_TIMEOUT = 30000; // 30 seconds var POOP_XP_LOSS_TIMEOUT = 60000; // 1 minute var BORED_XP_LOSS_TIMEOUT = 90000; // 1.5 minutes var XP_LOSS_INTERVAL = 90000; // 1.5 minutes interval for XP loss if player doesn't play with pet var xpLossTimer; // Timer for XP loss after not feeding var XP_LOSS_AMOUNT = 50; // Amount of XP to lose var poopTimer; // Timer for when pet will poop var activePoop = null; // Reference to the active poop object var poopSpeechTimer; // Timer for poop speech bubble var poopXpLossTimer; // Timer for XP loss from uncleaned poop var boredXpLossTimer; // Timer for XP loss when bored // Define game constants - calculate after loading state var BASE_XP_PER_LEVEL = 200; var XP_PER_LEVEL = BASE_XP_PER_LEVEL * Math.pow(2, petLevel - 1); // Doubles with each level var XP_GAIN_FEED = 20; var XP_GAIN_CLEAN = 15; var XP_GAIN_PLAY = 25; var COOLDOWN_TIME = 10000; // 10 seconds cooldown between actions var PLAY_INACTIVITY_XP_LOSS = 15; // Amount of XP to lose from play inactivity var playInactivityInterval; // Interval for recurring XP loss due to play inactivity var hpText; // Define hpText in global scope var barFill; // Define barFill in global scope var barBackground; // Define barBackground in global scope // Track which need is currently active var currentNeed = null; // "hunger", "boredom", "poop", "cheer" // New: For random need logic var NEEDS = ["hunger", "boredom", "poop", "cheer"]; // Vector2 positions for pet at different evolution levels var petBabyPosition = { x: 2048 / 2, y: 2732 / 2 + 250 }; var petChildPosition = { x: 2048 / 2, y: 2732 / 2 + 200 }; var petTeenPosition = { x: 2048 / 2, y: 2732 / 2 + 150 }; var petAdultPosition = { x: 2048 / 2, y: 2732 / 2 + 150 }; // Track current game state var progressBar = new Container(); var currentScreen = "menu"; var mainMenu, pet, progressBar, statusText, levelText; var currentMemoryGame = null; // To hold the active MemoryGame instance var isMemoryGameActive = false; // Track if memory game is active var feedButton, cleanButton, playButton; var progressBarContainer = new Container(); // Current pet position based on level var petPosition = petBabyPosition; // Initialize menu function initMainMenu() { LK.playMusic('bgmusic'); // Clear previous game elements if they exist clearGameElements(); // Always show main menu first mainMenu = new MainMenu(); game.addChild(mainMenu); // Set up callbacks setupMainMenuCallbacks(); // Function to set up main menu callbacks function setupMainMenuCallbacks() { // Set up start game callback mainMenu.onStartGame = function () { // Check if it's first time playing or reset game if (!storage.petName) { // First time playing, transition to character select tween(mainMenu, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { game.removeChild(mainMenu); // Show character select screen var characterSelect = new CharacterSelect(); game.addChild(characterSelect); // Handle character selection characterSelect.onSelectCharacter = function (selectedPetType) { // Show name selection screen tween(characterSelect, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { game.removeChild(characterSelect); // Create name selection screen var nameSelect = new NameSelect(selectedPetType); game.addChild(nameSelect); // Handle name selection nameSelect.onNameSelected = function (selectedName) { // Save the selected name storage.petName = selectedName; // Hide name select and start game tween(nameSelect, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { game.removeChild(nameSelect); // Now start the game currentScreen = "game"; initGameScreen(); } }); }; } }); }; } }); } else { // Returning player with saved data, go directly to gameplay currentScreen = "game"; initGameScreen(); } }; // Set up reset game callback mainMenu.onResetGame = function () { // Reset all storage values storage.petLevel = 1; petLevel = 1; // Also reset the global petLevel variable storage.petXp = 0; petXp = 0; // Reset the current petXp value storage.lastFed = 0; storage.lastCleaned = 0; storage.lastPlayed = 0; storage.isHungry = false; storage.isBored = false; storage.hasActivePoop = false; storage.petName = null; // Reset pet name to trigger character select again // Show confirmation text var confirmText = new Text2("Game data reset!", { size: 60, fill: 0xFFFFFF }); confirmText.anchor.set(0.5, 0.5); confirmText.x = 2048 / 2; confirmText.y = 2732 / 2 + 500; mainMenu.addChild(confirmText); // Fade out confirmation text tween(confirmText, { alpha: 0 }, { duration: 2000, onFinish: function onFinish() { mainMenu.removeChild(confirmText); } }); }; } } // This function is now defined inside initMainMenu // Initialize game screen function initGameScreen() { console.log("Initializing game screen"); // Clear previous elements clearGameElements(); // Add game background var gameBackground = LK.getAsset('game_background', { anchorX: 0.5, anchorY: 0.5 }); gameBackground.x = 2048 / 2; gameBackground.y = 2732 / 2; game.addChild(gameBackground); // Create game elements pet = new Pet(); pet.x = petPosition.x; pet.y = petPosition.y; game.addChild(pet); // Position pet name above level text pet.positionNameAboveLevel(); // Add mechanic: show image above and left of pet when clicked, and allow XP gain on image click every 5s var petClickImage = null; var lastPetClickImageXpTime = 0; // Track last time XP was given from image pet.interactive = true; pet.down = function (x, y, obj) { // Remove previous image if exists if (petClickImage && petClickImage.parent) { petClickImage.parent.removeChild(petClickImage); petClickImage = null; } // Create the image (now using 'cheer_image' for cheering) petClickImage = LK.getAsset('cheer_image', { anchorX: 0.5, anchorY: 0.5, width: 400, height: 400 }); // Position above and left of pet petClickImage.x = pet.x - pet.width / 2 - 100; petClickImage.y = pet.y - pet.height / 2 - 100; game.addChild(petClickImage); // Animate in (optional) petClickImage.alpha = 0; tween(petClickImage, { alpha: 1 }, { duration: 200 }); // Add interaction to the image for XP gain petClickImage.interactive = true; petClickImage.down = function (x2, y2, obj2) { // Visual feedback for press tween(petClickImage, { scaleX: 0.9, scaleY: 0.9 }, { duration: 80 }); }; petClickImage.up = function (x2, y2, obj2) { // Prevent cheering if memory game is active if (isMemoryGameActive) { statusText.showMessage("Finish the memory game first!"); return; } // Restore scale tween(petClickImage, { scaleX: 1, scaleY: 1 }, { duration: 80 }); var now = Date.now(); if (canCheerPet && isCheerNeeded) { // Fulfill cheer need addXp(20); statusText.showMessage("Pet cheered! +20 XP"); canCheerPet = false; isCheerNeeded = false; // Hide cheer speech bubble if visible and it's showing the cheer message if (speechBubble && speechBubble.alpha > 0 && (speechBubble.text === "Pet me! \nI need a cheer!" || speechBubble.text === "I feel lonely! \nPet me please!")) { LK.setTimeout(function () { speechBubble.hide(300); }, 3000); } // Schedule next need after cheer is fulfilled game.scheduleNextNeed(); // Clear cheer XP loss timer if (cheerXpLossTimer) { LK.clearTimeout(cheerXpLossTimer); cheerXpLossTimer = null; } // Pet reacts (bounce/happy animation) if (pet && typeof pet.happy === "function") { pet.happy(); } lastPetClickImageXpTime = now; } else if (!lastPetClickImageXpTime || now - lastPetClickImageXpTime >= 5000) { addXp(15); statusText.showMessage("Pet is happy! +15 XP"); lastPetClickImageXpTime = now; // Pet reacts (bounce/happy animation) if (pet && typeof pet.happy === "function") { pet.happy(); } } else { statusText.showMessage("Wait before cheering again!"); } }; // Hide after 1.5 seconds LK.setTimeout(function () { if (petClickImage && petClickImage.parent) { tween(petClickImage, { alpha: 0 }, { duration: 200, onFinish: function onFinish() { if (petClickImage && petClickImage.parent) { petClickImage.parent.removeChild(petClickImage); petClickImage = null; } } }); } }, 1500); }; // Level text levelText = new Text2("Level " + petLevel, { size: 80, fill: 0xFFFFFF }); levelText.anchor.set(0.5, 0.5); levelText.x = 2048 / 2; levelText.y = 200; game.addChild(levelText); // Create action buttons feedButton = new ActionButton('feed'); feedButton.x = 2048 / 4; feedButton.y = 2732 - 400; game.addChild(feedButton); cleanButton = new ActionButton('clean'); cleanButton.x = 2048 / 2; cleanButton.y = 2732 - 400; game.addChild(cleanButton); playButton = new ActionButton('play'); playButton.x = 2048 * 3 / 4; playButton.y = 2732 - 400; game.addChild(playButton); // Recalculate XP_PER_LEVEL based on current level XP_PER_LEVEL = BASE_XP_PER_LEVEL * Math.pow(2, petLevel - 1); console.log("Current petLevel:", petLevel, "XP_PER_LEVEL:", XP_PER_LEVEL); // Make sure all values are calculated before updating progress bar XP_PER_LEVEL = BASE_XP_PER_LEVEL * Math.pow(2, petLevel - 1); // Update the progress bar on start console.log("Initial progress bar update with petXp:", petXp, "XP_PER_LEVEL:", XP_PER_LEVEL); // Make sure variables are initialized properly petXp = petXp || 0; XP_PER_LEVEL = XP_PER_LEVEL || BASE_XP_PER_LEVEL; // Now create HP text after progress bar exists hpText = new Text2("XP: " + petXp + "/" + XP_PER_LEVEL, { size: 60, fill: 0xFFFFFF }); hpText.anchor.set(0, 0.5); hpText.x = progressBar.x + 10; hpText.y = progressBar.y - 50; game.addChild(hpText); // Create HP text display console.log("Creating HP text with petXp:", petXp, "XP_PER_LEVEL:", XP_PER_LEVEL); hpText = new Text2("XP: " + petXp + "/" + XP_PER_LEVEL, { size: 60, fill: 0xFFFFFF }); hpText.anchor.set(0, 0.5); game.addChild(hpText); console.log("Game screen initialization complete"); createProgressBar(); addBarFillXp(); // Status text statusText = new StatusText(""); statusText.x = 2048 / 2; statusText.y = progressBar.y + 100; statusText.alpha = 0; game.addChild(statusText); // Create speech bubble for needs speechBubble = new BubbleSpeech(""); speechBubble.x = pet.x + 450; speechBubble.y = pet.y - 450; // Position above pet speechBubble.alpha = 0; // Start invisible game.addChild(speechBubble); // Helper: clear any need timers function clearNeedTimers() { if (needTimer) { LK.clearTimeout(needTimer); needTimer = null; } if (needTimeout) { LK.clearTimeout(needTimeout); needTimeout = null; } if (xpLossTimer) { LK.clearTimeout(xpLossTimer); xpLossTimer = null; } if (boredXpLossTimer) { LK.clearTimeout(boredXpLossTimer); boredXpLossTimer = null; } if (playInactivityInterval) { LK.clearInterval(playInactivityInterval); playInactivityInterval = null; } if (poopSpeechTimer) { LK.clearTimeout(poopSpeechTimer); poopSpeechTimer = null; } if (poopXpLossTimer) { LK.clearTimeout(poopXpLossTimer); poopXpLossTimer = null; } if (cheerXpLossTimer) { LK.clearTimeout(cheerXpLossTimer); cheerXpLossTimer = null; } canCheerPet = false; isCheerNeeded = false; } // Helper: pick a random need function pickRandomNeed() { // If there is active poop, always return "poop" if (activePoop) { return "poop"; } // Increase probability of 'boredom' by weighting the array // e.g. ["hunger", "boredom", "boredom", "boredom", "cheer"] var weightedNeeds = ["hunger", "boredom", "boredom", "boredom", "cheer"]; var idx = Math.floor(Math.random() * weightedNeeds.length); return weightedNeeds[idx]; } // Helper: schedule the next random need after a random delay (15-30s) function scheduleNextNeed() { var nextDelay = 15000 + Math.floor(Math.random() * 15000); // 15-30s needTimer = LK.setTimeout(function () { showRandomNeed(); }, nextDelay); } // Assign to game so it can be called as game.scheduleNextNeed() game.scheduleNextNeed = scheduleNextNeed; // Main: show a random need function showRandomNeed() { // If memory game is active, wait and try again later if (isMemoryGameActive) { // Try again in 2 seconds needTimer = LK.setTimeout(function () { showRandomNeed(); }, 2000); return; } clearNeedTimers(); // Pick a random need var need = pickRandomNeed(); currentNeed = need; canFeedPet = true; canPlayPet = false; canCleanPet = false; isHungry = false; isBored = false; // Show the bubble and set the action variable if (need === "hunger") { speechBubble.setText("I'm hungry!"); speechBubble.show(500); canFeedPet = true; isHungry = true; // XP loss if ignored xpLossTimer = LK.setTimeout(function () { if (petXp > 0) { addXp(-XP_LOSS_AMOUNT); statusText.showMessage("Pet is hungry! Lost " + XP_LOSS_AMOUNT + " XP!"); } }, XP_LOSS_TIMEOUT); } else if (need === "boredom") { speechBubble.setText("I'm bored! \nPlay with me!"); speechBubble.show(500); canPlayPet = true; isBored = true; // XP loss if ignored boredXpLossTimer = LK.setTimeout(function () { if (petXp > 0) { addXp(-XP_LOSS_AMOUNT); statusText.showMessage("Pet is bored! Lost " + XP_LOSS_AMOUNT + " XP!"); } }, BORED_XP_LOSS_TIMEOUT); // Play inactivity XP loss playInactivityInterval = LK.setInterval(function () { var currentTime = Date.now(); var timeSinceLastPlayed = currentTime - lastPlayed; if (timeSinceLastPlayed > 300000 && petXp > 0) { addXp(-PLAY_INACTIVITY_XP_LOSS); statusText.showMessage("Your pet misses you! Lost " + PLAY_INACTIVITY_XP_LOSS + " XP!"); speechBubble.setText("I miss \nplaying \nwith you!"); speechBubble.show(500); LK.setTimeout(function () { speechBubble.hide(300); }, 3000); } }, XP_LOSS_INTERVAL); } else if (need === "cheer") { speechBubble.setText("Pet me! \nI need a cheer!"); speechBubble.show(500); canCheerPet = true; isCheerNeeded = true; // XP loss if ignored cheerXpLossTimer = LK.setTimeout(function () { if (petXp > 0) { addXp(-XP_LOSS_AMOUNT); statusText.showMessage("Pet feels lonely! Lost " + XP_LOSS_AMOUNT + " XP!"); speechBubble.setText("I feel lonely! \nPet me please!"); speechBubble.show(500); LK.setTimeout(function () { speechBubble.hide(300); }, 3000); } }, BORED_XP_LOSS_TIMEOUT); } else if (need === "poop") { // If no active poop, spawn one if (!activePoop) { activePoop = new Poop(); activePoop.x = pet.x + 900; activePoop.y = pet.y + 200; game.addChild(activePoop); activePoop.appear(); // Set canCleanPet to true and enable cleanButton when poop spawns canCleanPet = true; if (cleanButton) { cleanButton.interactive = true; } } speechBubble.setText("Please clean \nthis mess!"); speechBubble.show(500); // Set canCleanPet to true when poop and clean speech bubble appear canCleanPet = true; // XP loss if ignored poopXpLossTimer = LK.setTimeout(function () { var loseXpInterval = LK.setInterval(function () { if (activePoop) { if (petXp > 0) { addXp(-XP_LOSS_AMOUNT); statusText.showMessage("Pet uncomfortable! Lost " + XP_LOSS_AMOUNT + " XP!"); speechBubble.setText("This mess \nis making \nme lose XP!"); speechBubble.show(500); LK.setTimeout(function () { speechBubble.hide(300); }, 3000); } } else { LK.clearInterval(loseXpInterval); } }, POOP_XP_LOSS_TIMEOUT); }, POOP_XP_LOSS_TIMEOUT); } // Hide every speech bubble after 3 seconds LK.setTimeout(function () { speechBubble.hide(300); }, 3000); } // If there was an active poop, restore it if (activePoop) { activePoop = new Poop(); activePoop.x = pet.x + Math.random() * 1000; activePoop.y = pet.y + 100 + Math.random() * 50; game.addChild(activePoop); activePoop.appear(); } // Start the random need loop showRandomNeed(); } //[3J] // Clear all game elements // Function to stop a specific timer by name function stopTimer(timerName) { // Check if timer name is valid before attempting to stop if (!timerName) { console.log("Cannot stop timer: timer name is undefined or null"); return; } if (timerName === 'hunger' && hungerTimer) { LK.clearTimeout(hungerTimer); hungerTimer = null; console.log("Hunger timer stopped"); } else if (timerName === 'xpLoss' && xpLossTimer) { LK.clearTimeout(xpLossTimer); xpLossTimer = null; console.log("XP loss timer stopped"); } else if (timerName === 'poop' && poopTimer) { LK.clearTimeout(poopTimer); poopTimer = null; console.log("Poop timer stopped"); } else if (timerName === 'poopSpeech' && poopSpeechTimer) { LK.clearTimeout(poopSpeechTimer); poopSpeechTimer = null; console.log("Poop speech timer stopped"); } else if (timerName === 'poopXpLoss' && poopXpLossTimer) { LK.clearTimeout(poopXpLossTimer); poopXpLossTimer = null; console.log("Poop XP loss timer stopped"); } else if (timerName === 'bored' && boredTimer) { LK.clearTimeout(boredTimer); boredTimer = null; console.log("Bored timer stopped"); } else if (timerName === 'boredXpLoss' && boredXpLossTimer) { LK.clearTimeout(boredXpLossTimer); boredXpLossTimer = null; console.log("Bored XP loss timer stopped"); } else if (timerName === 'playInactivity' && playInactivityInterval) { LK.clearInterval(playInactivityInterval); playInactivityInterval = null; console.log("Play inactivity interval stopped"); } else { console.log("Timer not found or already stopped: " + timerName); } } function clearGameElements() { // Clear hunger timer if exists if (hungerTimer) { LK.clearTimeout(hungerTimer); hungerTimer = null; } // Clear hunger speech bubble interval if exists if (typeof hungerSpeechBubbleInterval !== "undefined" && hungerSpeechBubbleInterval) { LK.clearInterval(hungerSpeechBubbleInterval); hungerSpeechBubbleInterval = null; } // Clear XP loss timer if exists if (xpLossTimer) { LK.clearTimeout(xpLossTimer); xpLossTimer = null; } // Clear poop timer if exists if (poopTimer) { LK.clearTimeout(poopTimer); poopTimer = null; } // Clear poop speech timer if exists if (poopSpeechTimer) { LK.clearTimeout(poopSpeechTimer); poopSpeechTimer = null; } // Clear poop XP loss timer if exists if (poopXpLossTimer) { LK.clearTimeout(poopXpLossTimer); poopXpLossTimer = null; } // Clear bored timer if exists if (boredTimer) { LK.clearTimeout(boredTimer); boredTimer = null; } // Clear bored XP loss timer if exists if (boredXpLossTimer) { LK.clearTimeout(boredXpLossTimer); boredXpLossTimer = null; } // Clear play inactivity interval if exists if (playInactivityInterval) { LK.clearInterval(playInactivityInterval); playInactivityInterval = null; } // Clear cheer XP loss timer if exists if (cheerXpLossTimer) { LK.clearTimeout(cheerXpLossTimer); cheerXpLossTimer = null; } canCheerPet = false; isCheerNeeded = false; // Reset active poop reference activePoop = null; while (game.children.length > 0) { game.removeChild(game.children[0]); } //[3K] } //[3L] // Start with main menu initMainMenu(); function performFeedAction() { console.log("performFeedAction called"); var currentTime = Date.now(); //[3O] // Check if pet is recently fed (on cooldown) if (currentTime - lastFed < COOLDOWN_TIME) { statusText.showMessage("Pet is still full! Wait a moment."); console.log("Action on cooldown, can't feed yet"); return; //[3P] } //[3Q] // Check if feeding is allowed using canFeedPet variable if (!canFeedPet) { statusText.showMessage("Pet isn't hungry yet!"); console.log("Pet can't be fed yet - canFeedPet is false"); return; } // If hunger message is showing, we can feed the pet if (speechBubble && speechBubble.alpha > 0 && speechBubble.text === "I'm hungry!") { // Pet is hungry with message showing, continue with feeding } else if (!canFeedPet) { statusText.showMessage("Pet isn't hungry yet!"); console.log("Pet isn't hungry yet"); return; } console.log("Feeding pet, adding XP:", XP_GAIN_FEED); LK.getSound('feed').play(); pet.happy(); //[3R] lastFed = currentTime; storage.lastFed = lastFed; addXp(XP_GAIN_FEED); console.log("After addXp call"); statusText.showMessage("Pet fed! +20 XP"); // Set canFeedPet to false after feeding canFeedPet = false; // Update hunger state in storage storage.isHungry = false; // Hide hunger speech bubble if visible and it's showing the hunger message if (speechBubble && speechBubble.alpha > 0 && speechBubble.text === "I'm hungry!") { LK.setTimeout(function () { speechBubble.hide(300); }, 3000); } // Schedule next need after feeding is resolved game.scheduleNextNeed(); // Clear the repeated hunger speech bubble interval if it exists if (typeof hungerSpeechBubbleInterval !== "undefined" && hungerSpeechBubbleInterval) { LK.clearInterval(hungerSpeechBubbleInterval); hungerSpeechBubbleInterval = null; } // Reset hunger timer if (hungerTimer) { LK.clearTimeout(hungerTimer); } hungerTimer = LK.setTimeout(function () { // Show speech bubble with animation speechBubble.show(500); // Set canFeedPet to true when hunger message appears again canFeedPet = true; // Update hunger state in storage storage.isHungry = true; console.log("Pet is hungry again - canFeedPet set to true"); }, 60000); // Reset XP loss timer when feeding if (xpLossTimer) { LK.clearTimeout(xpLossTimer); } xpLossTimer = LK.setTimeout(function () { if (petXp > 0) { addXp(-XP_LOSS_AMOUNT); statusText.showMessage("Pet is hungry! Lost " + XP_LOSS_AMOUNT + " XP!"); } }, XP_LOSS_TIMEOUT); // Set timer for pet to poop 15 seconds after being fed every time if (poopTimer) { LK.clearTimeout(poopTimer); } poopTimer = LK.setTimeout(function () { // Only create poop if there isn't one already if (!activePoop) { // Create new poop slightly behind the pet activePoop = new Poop(); activePoop.x = pet.x + 1000; // Random position near pet activePoop.y = pet.y + 200; game.addChild(activePoop); activePoop.appear(); // Update poop state in storage storage.hasActivePoop = true; // Create a speech bubble for the poop event speechBubble.setText("I made \na mess!"); speechBubble.show(500); // Hide speech bubble after 3 seconds LK.setTimeout(function () { speechBubble.hide(300); }, 3000); // Set timer to show clean up reminder after 1 minute if (poopSpeechTimer) { LK.clearTimeout(poopSpeechTimer); } poopSpeechTimer = LK.setTimeout(function () { speechBubble.setText("Please clean \nthis mess!"); speechBubble.show(500); }, POOP_SPEECH_TIMEOUT); // Set timer for XP loss after 2 minutes of not cleaning if (poopXpLossTimer) { LK.clearTimeout(poopXpLossTimer); } poopXpLossTimer = LK.setTimeout(function () { // Set up recurring XP loss every 2 minutes until cleaned var loseXpInterval = LK.setInterval(function () { if (activePoop) { if (petXp > 0) { addXp(-XP_LOSS_AMOUNT); statusText.showMessage("Pet uncomfortable! Lost " + XP_LOSS_AMOUNT + " XP!"); // Show speech bubble about being uncomfortable speechBubble.setText("This mess \nis making \nme lose XP!"); speechBubble.show(500); LK.setTimeout(function () { speechBubble.hide(300); }, 3000); } } else { // Clear interval if poop is gone LK.clearInterval(loseXpInterval); } }, POOP_XP_LOSS_TIMEOUT); }, POOP_XP_LOSS_TIMEOUT); } }, 15000); // Always 15 seconds after eating } //[3S] function performCleanAction() { var currentTime = Date.now(); if (currentTime - lastCleaned < COOLDOWN_TIME) { statusText.showMessage("Pet is already clean! Wait a moment."); return; } // Only allow cleaning if canCleanPet is true if (!canCleanPet) { statusText.showMessage("Pet doesn't need cleaning right now!"); return; } // Check if there's poop to clean if (activePoop) { LK.getSound('clean').play(); pet.happy(); lastCleaned = currentTime; storage.lastCleaned = lastCleaned; // Add extra XP for cleaning poop addXp(XP_GAIN_CLEAN * 2); statusText.showMessage("Poop cleaned! +30 XP"); // Animate poop cleanup activePoop.cleanup(function () { if (activePoop && activePoop.parent) { activePoop.parent.removeChild(activePoop); } // Clear poop speech bubble timer if (poopSpeechTimer) { LK.clearTimeout(poopSpeechTimer); poopSpeechTimer = null; } // Clear poop XP loss timer if (poopXpLossTimer) { LK.clearTimeout(poopXpLossTimer); poopXpLossTimer = null; } activePoop = null; // Update poop state in storage storage.hasActivePoop = false; canCleanPet = false; // Reset after cleaning // Schedule next need after cleaning is resolved game.scheduleNextNeed(); }); // Pet is happy about cleaning LK.setTimeout(function () { // Save previous speech bubble text so we know if it was about cleaning var previousText = speechBubble.text; speechBubble.setText("Thank you!"); speechBubble.show(500); // Hide speech bubble after 3 seconds, but only if it was about cleaning LK.setTimeout(function () { // Only hide if we had shown the thank you message from cleaning if (speechBubble.text === "Thank you!" && (previousText === "Please clean \nthis mess!" || previousText === "I made \na mess!" || previousText === "This mess \nis making \nme lose XP!")) { speechBubble.hide(300); } }, 3000); }, 500); } else { // Don't allow cleaning if there's no poop statusText.showMessage("Nothing to clean right now!"); } } function performPlayAction() { var currentTime = Date.now(); if (currentTime - lastPlayed < COOLDOWN_TIME) { statusText.showMessage("Pet is tired! Wait a moment."); return; } // Check if playing is allowed using canPlayPet variable if (!canPlayPet) { statusText.showMessage("Pet isn't bored yet!"); console.log("Pet can't be played with yet - canPlayPet is false"); return; } // Decide if memory game should appear (2 out of 3 times) if (typeof playMemoryGameCounter === "undefined") { playMemoryGameCounter = 0; } playMemoryGameCounter++; var playMemoryGame = false; if (playMemoryGameCounter >= 3) { playMemoryGameCounter = 1; } if (playMemoryGameCounter === 1 || playMemoryGameCounter === 2) { playMemoryGame = true; } LK.getSound('play').play(); pet.happy(); lastPlayed = currentTime; storage.lastPlayed = lastPlayed; if (playMemoryGame) { // Launch memory game for extra XP speechBubble.setText("Let's play a \nmemory \ngame!"); speechBubble.show(500); // Pause all timers before starting memory game var savedTimers = { hungerTimer: hungerTimer, xpLossTimer: xpLossTimer, boredTimer: boredTimer, boredXpLossTimer: boredXpLossTimer, poopTimer: poopTimer, poopSpeechTimer: poopSpeechTimer, poopXpLossTimer: poopXpLossTimer }; // Clear all active timers if (hungerTimer) { LK.clearTimeout(hungerTimer); } if (xpLossTimer) { LK.clearTimeout(xpLossTimer); } if (boredTimer) { LK.clearTimeout(boredTimer); } if (boredXpLossTimer) { LK.clearTimeout(boredXpLossTimer); } if (poopTimer) { LK.clearTimeout(poopTimer); } if (poopSpeechTimer) { LK.clearTimeout(poopSpeechTimer); } if (poopXpLossTimer) { LK.clearTimeout(poopXpLossTimer); } if (playInactivityInterval) { LK.clearInterval(playInactivityInterval); } // Disable action buttons during memory game feedButton.interactive = false; cleanButton.interactive = false; playButton.interactive = false; isMemoryGameActive = true; var memoryGame = new MemoryGame(); // Add delay before creating and showing memory game LK.setTimeout(function () { // Create and start memory game game.addChild(memoryGame); }, 1500); // 0.5 second delay // Handle game completion memoryGame.onComplete = function (score) { isMemoryGameActive = false; // Re-enable action buttons when game completes feedButton.interactive = true; cleanButton.interactive = true; playButton.interactive = true; // Only add XP if the player completes the memory game with a score of at least 50 if (score >= 50) { // Add XP based on score (higher score = more XP) var earnedXp = XP_GAIN_PLAY + score; addXp(earnedXp); statusText.showMessage("Beat memory game! +" + earnedXp + " XP"); // Tell player how they did speechBubble.setText("Great job! \nYou earned \n" + earnedXp + " XP!"); // Pet is happy after successful game completion pet.happy(); speechBubble.show(500); // Hide speech bubble after 2 seconds, but only if it's related to boredom LK.setTimeout(function () { if (speechBubble && speechBubble.text && speechBubble.text.includes("Great job!")) { speechBubble.hide(300); } }, 2000); // Set canPlayPet to false after playing canPlayPet = false; // Update boredom state in storage storage.isBored = false; // Stop the boredXpLoss timer when winning the memory game if (boredXpLossTimer) { LK.clearTimeout(boredXpLossTimer); boredXpLossTimer = null; } // Reset boredom timer if (boredTimer) { LK.clearTimeout(boredTimer); } boredTimer = LK.setTimeout(function () { speechBubble.setText("I'm bored! \n Play with me!"); speechBubble.show(500); canPlayPet = true; storage.isBored = true; console.log("Pet is bored again - canPlayPet set to true"); }, 60000); if (boredTimer) { LK.clearTimeout(boredTimer); } boredTimer = LK.setTimeout(function () { speechBubble.setText("I'm bored! \n Play with me!"); speechBubble.show(500); canPlayPet = true; storage.isBored = true; }, 60000); if (xpLossTimer) { LK.clearTimeout(xpLossTimer); } xpLossTimer = LK.setTimeout(function () { if (petXp > 0) { addXp(-XP_LOSS_AMOUNT); statusText.showMessage("Pet is hungry! Lost " + XP_LOSS_AMOUNT + " XP!"); } }, XP_LOSS_TIMEOUT); } else { // Player quit or didn't complete the game statusText.showMessage("You must \ncomplete \nthe game!"); speechBubble.setText("Let's try \nagain!"); speechBubble.show(500); // Wait a moment and then start a new memory game LK.setTimeout(function () { var newMemoryGame = new MemoryGame(); game.addChild(newMemoryGame); newMemoryGame.onComplete = memoryGame.onComplete; }, 2000); return; } }; // Reset bored XP loss timer if (boredXpLossTimer) { LK.clearTimeout(boredXpLossTimer); } boredXpLossTimer = LK.setTimeout(function () { if (petXp > 0) { addXp(-XP_LOSS_AMOUNT); statusText.showMessage("Pet is bored! Lost " + XP_LOSS_AMOUNT + " XP!"); } }, BORED_XP_LOSS_TIMEOUT); // Reset play inactivity interval to prevent additional XP loss if (playInactivityInterval) { LK.clearInterval(playInactivityInterval); } playInactivityInterval = LK.setInterval(function () { var currentTime = Date.now(); var timeSinceLastPlayed = currentTime - lastPlayed; if (timeSinceLastPlayed > 300000 && petXp > 0) { addXp(-PLAY_INACTIVITY_XP_LOSS); statusText.showMessage("Your pet misses you! Lost " + PLAY_INACTIVITY_XP_LOSS + " XP!"); speechBubble.setText("I miss playing `\n with you!"); speechBubble.show(500); LK.setTimeout(function () { speechBubble.hide(300); }, 3000); } }, XP_LOSS_INTERVAL); return; } // If not memory game, just play and give normal XP addXp(XP_GAIN_PLAY); statusText.showMessage("Played with pet! +" + XP_GAIN_PLAY + " XP"); speechBubble.setText("Yay! \nThat was fun!"); speechBubble.show(500); LK.setTimeout(function () { speechBubble.hide(300); }, 3000); canPlayPet = false; storage.isBored = false; if (boredXpLossTimer) { LK.clearTimeout(boredXpLossTimer); boredXpLossTimer = null; } if (boredTimer) { LK.clearTimeout(boredTimer); } boredTimer = LK.setTimeout(function () { speechBubble.setText("I'm bored! \n Play with me!"); speechBubble.show(500); canPlayPet = true; storage.isBored = true; }, 60000); if (boredTimer) { LK.clearTimeout(boredTimer); } boredTimer = LK.setTimeout(function () { speechBubble.setText("I'm bored! \n Play with me!"); speechBubble.show(500); canPlayPet = true; storage.isBored = true; }, 60000); if (xpLossTimer) { LK.clearTimeout(xpLossTimer); } xpLossTimer = LK.setTimeout(function () { if (petXp > 0) { addXp(-XP_LOSS_AMOUNT); statusText.showMessage("Pet is hungry! Lost " + XP_LOSS_AMOUNT + " XP!"); } }, XP_LOSS_TIMEOUT); // Schedule next need after play is resolved game.scheduleNextNeed(); } function addXp(amount) { console.log("addXp called with amount:", amount); petXp += amount; storage.petXp = petXp; // Check if pet is at level 1 and XP goes below 0 if (petLevel === 1 && petXp < 0) { // Pet dies at level 1 with negative XP statusText.showMessage(storage.petName + " has died!"); // Show game over LK.setTimeout(function () { // Reset all storage values storage.petLevel = 1; storage.petXp = 0; storage.lastFed = 0; storage.lastCleaned = 0; storage.lastPlayed = 0; storage.isHungry = false; storage.isBored = false; storage.hasActivePoop = false; storage.petName = null; // Reset pet name to trigger character select again // Show game over screen LK.showGameOver(); }, 2000); return; // Stop execution } // Check if pet level is greater than 1 and XP goes below 0 if (petLevel > 1 && petXp < 0) { // Pet regresses to previous level petLevel -= 1; // Calculate new XP_PER_LEVEL for the previous level XP_PER_LEVEL = BASE_XP_PER_LEVEL * Math.pow(2, petLevel - 1); // Calculate how much XP to subtract from previous level's total // If XP is -50 and pet was at level 2, it should have XP_PER_LEVEL - 50 (e.g., 500 - 50 = 450) petXp = XP_PER_LEVEL + petXp; // Add negative XP to previous level's max XP // Update storage values storage.petLevel = petLevel; storage.petXp = petXp; // Update level text levelText.setText("Level " + petLevel); // Update pet position based on new level if (petLevel === 1) { petPosition = petBabyPosition; } else if (petLevel === 2) { petPosition = petChildPosition; } else if (petLevel === 3) { petPosition = petTeenPosition; } else if (petLevel >= 4) { petPosition = petAdultPosition; } // Update pet position pet.x = petPosition.x; pet.y = petPosition.y; // Show message about regression statusText.showMessage("Pet regressed to level " + petLevel + "!"); // Update pet appearance // Remove old graphic and add new one var petType = storage.petType || "Fluffy"; var newPetAssetId; if (petType === "Puffy") { if (petLevel === 1) { newPetAssetId = 'pet_puffy_baby'; } else if (petLevel === 2) { newPetAssetId = 'pet_puffy_child'; } else if (petLevel === 3) { newPetAssetId = 'pet_puffy_teen'; } else if (petLevel >= 4) { newPetAssetId = 'pet_puffy_adult'; } } else { // Default for Fluffy pet if (petLevel === 1) { newPetAssetId = 'pet_baby'; } else if (petLevel === 2) { newPetAssetId = 'pet_child'; } else if (petLevel === 3) { newPetAssetId = 'pet_teen'; } else if (petLevel >= 4) { newPetAssetId = 'pet_adult'; } } // Get reference to current graphics to replace var currentGraphics = pet.children.find(function (child) { return child.asset && child.asset.indexOf('pet_') === 0; }); if (currentGraphics) { // Set the current pet image as invisible currentGraphics.visible = false; pet.removeChild(currentGraphics); } var petGraphics = pet.attachAsset(newPetAssetId, { anchorX: 0.5, anchorY: 0.5 }); // Flash effect to indicate regression LK.effects.flashObject(pet, 0xFF0000, 1000); } // Check for level up if (petXp >= XP_PER_LEVEL) { console.log("Level up condition met!"); petLevel += 1; petXp = 0; storage.petLevel = petLevel; storage.petXp = petXp; // Recalculate XP_PER_LEVEL for next level - doubles with each level XP_PER_LEVEL = BASE_XP_PER_LEVEL * Math.pow(2, petLevel - 1); console.log("New level:", petLevel, "New XP_PER_LEVEL:", XP_PER_LEVEL); // Update pet position based on new level if (petLevel === 2) { petPosition = petChildPosition; } else if (petLevel === 3) { petPosition = petTeenPosition; } else if (petLevel >= 4) { petPosition = petAdultPosition; } // Update pet position pet.x = petPosition.x; pet.y = petPosition.y; levelText.setText("Level " + petLevel); pet.evolve(); if (petLevel >= 4) { statusText.showMessage("Maximum level reached!"); } else { statusText.showMessage("Pet evolved to level " + petLevel + "! Next level: " + XP_PER_LEVEL + " XP"); } } // Update the HP text with current XP if (hpText) { hpText.setText("XP: " + petXp + "/" + XP_PER_LEVEL); } else { console.log("hpText is undefined or null"); } addBarFillXp(); } function createProgressBar() { // Initialize position first progressBar.x = 2048 / 2 - 400; // Center it progressBar.y = 2732 - 200; // Near bottom barBackground = LK.getAsset('progressBar_bg', { anchorX: 0, anchorY: 0.5 }); progressBar.addChild(barBackground); barFill = LK.getAsset('progressBar_fill', { anchorX: 0, anchorY: 0.5 }); progressBar.addChild(barFill); // Add progress bar to game game.addChild(progressBar); // Update hpText position now that progressBar exists if (hpText) { hpText.x = progressBar.x + 10; hpText.y = progressBar.y - 50; } } function addBarFillXp() { // Store current width to animate from var currentWidth = barFill.width; // Calculate target width var targetWidth = petXp / XP_PER_LEVEL * barBackground.width; // Animate the width change tween(barFill, { width: targetWidth }, { duration: 800, easing: tween.easeOutQuad }); } // Game update function game.update = function () { // Screen-specific updates if (currentScreen === "game") { // Ensure we're not continuously updating the progress bar every frame // This was the source of the undefined parameters call } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
petLevel: 1,
petXp: 0,
lastFed: 0,
lastCleaned: 0,
lastPlayed: 0,
petName: "undefined",
petType: "Fluffy"
});
/****
* Classes
****/
var ActionButton = Container.expand(function (actionType, position) {
var self = Container.call(this);
var buttonShape;
if (actionType === 'feed') {
buttonShape = self.attachAsset('foodItem', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (actionType === 'clean') {
buttonShape = self.attachAsset('cleaningItem', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (actionType === 'play') {
buttonShape = self.attachAsset('playItem', {
anchorX: 0.5,
anchorY: 0.5
});
}
self.buttonType = actionType;
self.interactive = true;
self.down = function (x, y, obj) {
tween(buttonShape, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 100
});
};
self.up = function (x, y, obj) {
tween(buttonShape, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
onFinish: function onFinish() {
if (self.buttonType === 'feed') {
performFeedAction();
} else if (self.buttonType === 'clean') {
performCleanAction();
} else if (self.buttonType === 'play') {
performPlayAction();
}
}
});
};
return self;
});
var BubbleSpeech = Container.expand(function (text) {
var self = Container.call(this);
// Create the bubble background
var bubbleBackground = LK.getAsset('bubble_speech', {
anchorX: 0.5,
anchorY: 0.5,
width: 600,
height: 600,
tint: 0xFFFFFF
});
// Round the corners by scaling
bubbleBackground.scale.set(1);
self.addChild(bubbleBackground);
// Add text to bubble
var bubbleText = new Text2(text || "", {
size: 80,
fill: 0x000000,
align: "center",
// You can set alignment to "left", "center", or "right"
wordWrap: true,
wordWrapWidth: 800 // Set this to control where text will wra
});
bubbleText.anchor.set(0.5, 0.5);
self.addChild(bubbleText);
// Public methods
self.setText = function (newText) {
bubbleText.setText(newText);
};
// Show with animation
self.show = function (duration) {
self.alpha = 0;
self.scale.set(0.5);
tween(self, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: duration || 300,
easing: tween.easeOutBack
});
};
// Hide with animation
self.hide = function (duration, callback) {
tween(self, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: duration || 300,
easing: tween.easeInBack,
onFinish: function onFinish() {
if (callback) {
callback();
}
}
});
};
return self;
});
var CharacterSelect = Container.expand(function () {
var self = Container.call(this);
// Background image
var background = self.attachAsset('menu_background', {
anchorX: 0.5,
anchorY: 0.5
});
background.x = 2048 / 2;
background.y = 2732 / 2;
// Title text
var titleText = new Text2("Choose Your Pet", {
size: 100,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
titleText.y = 500;
self.addChild(titleText);
// Create character options
var character1 = self.attachAsset('pet_select_1', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 3,
y: 2732 / 2
});
character1.interactive = true;
var character2 = self.attachAsset('pet_select_2', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 * 2 / 3,
y: 2732 / 2
});
character2.interactive = true;
// Character 1 label
var label1 = new Text2("Fluffy", {
size: 70,
fill: 0xFFFFFF
});
label1.anchor.set(0.5, 0.5);
label1.x = character1.x;
label1.y = character1.y + 350;
self.addChild(label1);
// Character 2 label
var label2 = new Text2("Puffy", {
size: 70,
fill: 0xFFFFFF
});
label2.anchor.set(0.5, 0.5);
label2.x = character2.x;
label2.y = character2.y + 350;
self.addChild(label2);
// Button animations and interaction
character1.down = function (x, y, obj) {
tween(character1, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100
});
};
character1.up = function (x, y, obj) {
tween(character1, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
onFinish: function onFinish() {
if (self.onSelectCharacter) {
storage.petType = 'Fluffy';
storage.petXp = 0;
storage.petLevel = 1;
self.onSelectCharacter('Fluffy');
}
}
});
};
character2.down = function (x, y, obj) {
tween(character2, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100
});
};
character2.up = function (x, y, obj) {
tween(character2, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
onFinish: function onFinish() {
if (self.onSelectCharacter) {
storage.petType = 'Puffy';
storage.petXp = 0;
storage.petLevel = 1;
self.onSelectCharacter('Puffy');
}
}
});
};
return self;
});
var MainMenu = Container.expand(function () {
var self = Container.call(this);
// Background image
var background = self.attachAsset('menu_background', {
anchorX: 0.5,
anchorY: 0.5
});
background.x = 2048 / 2;
background.y = 2732 / 2;
// Game logo
var gameLogo = self.attachAsset('game_logo', {
anchorX: 0.5,
anchorY: 0.5
});
gameLogo.x = 2048 / 2;
gameLogo.y = 800;
// Play button image
var playButton = self.attachAsset('play_button', {
anchorX: 0.5,
anchorY: 0.5
});
playButton.x = 2048 / 2;
playButton.y = 2732 / 2 + 200;
playButton.interactive = true;
// Reset button
var resetButton = new Text2("Reset Game", {
size: 60,
fill: 0xFFFFFF
});
resetButton.anchor.set(0.5, 0.5);
resetButton.x = 2048 / 2;
resetButton.y = 2732 / 2 + 400;
resetButton.interactive = true;
self.addChild(resetButton);
// Button animations and interaction
playButton.down = function (x, y, obj) {
tween(playButton, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100
});
};
playButton.up = function (x, y, obj) {
tween(playButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
onFinish: function onFinish() {
if (self.onStartGame) {
self.onStartGame();
}
}
});
};
// Reset button interactions
resetButton.down = function (x, y, obj) {
tween(resetButton, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100
});
};
resetButton.up = function (x, y, obj) {
tween(resetButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
onFinish: function onFinish() {
if (self.onResetGame) {
self.onResetGame();
}
}
});
};
// Add some animation to the logo
function animateLogo() {
tween(gameLogo, {
y: gameLogo.y - 15
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(gameLogo, {
y: gameLogo.y + 15
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: animateLogo
});
}
});
}
// Start the animation
animateLogo();
return self;
});
var MemoryCard = Container.expand(function (cardValue, cardBack) {
var self = Container.call(this);
self.value = cardValue; // Card match value
self.revealed = false; // Is card face up
self.matched = false; // Is card matched
// Create card front (colored shape)
self.front = LK.getAsset('memory_card_' + cardValue, {
anchorX: 0.5,
anchorY: 0.5
});
self.front.visible = false;
self.addChild(self.front);
// Create card back
self.back = LK.getAsset('memory_card_back', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(self.back);
// Make card interactive
self.interactive = true;
// Flip card to show front
self.reveal = function () {
if (self.matched || self.revealed) {
return;
} // Already revealed or matched
self.revealed = true;
// Animate card flip
tween(self.scale, {
x: 0
}, {
duration: 150,
easing: tween.easeInQuad,
onFinish: function onFinish() {
self.front.visible = true;
self.back.visible = false;
tween(self.scale, {
x: 1
}, {
duration: 150,
easing: tween.easeOutQuad
});
}
});
if (self.onReveal) {
self.onReveal(self);
}
};
// Flip card to hide front
self.hide = function () {
if (self.matched) {
return;
} // Don't hide if matched
// Animate card flip
tween(self.scale, {
x: 0
}, {
duration: 150,
easing: tween.easeInQuad,
onFinish: function onFinish() {
self.front.visible = false;
self.back.visible = true;
self.revealed = false;
tween(self.scale, {
x: 1
}, {
duration: 150,
easing: tween.easeOutQuad
});
}
});
};
// Mark card as matched
self.setMatched = function () {
self.matched = true;
self.interactive = false;
// Flash effect for matching
tween(self, {
alpha: 0.5
}, {
duration: 200,
easing: tween.easeInQuad,
onFinish: function onFinish() {
tween(self, {
alpha: 1
}, {
duration: 200,
easing: tween.easeOutQuad
});
}
});
};
// Handle card click
self.down = function (x, y, obj) {
// If card is already matched, or not interactive, do nothing.
if (self.matched || !self.interactive) {
return;
}
// If card is already revealed (face up), do nothing on 'down'.
if (self.revealed) {
return;
}
// If two cards are already face up (checked via currentMemoryGame.canSelectCard()),
// do not allow this card (which is currently face down) to be pressed/animated.
// currentMemoryGame might be null if the game is not active, so check it.
if (currentMemoryGame && !currentMemoryGame.canSelectCard()) {
return;
}
// Card is face down, interactive, not matched, and we are allowed to select a card.
// Animate press down.
tween(self, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100
});
};
self.up = function (x, y, obj) {
// If card is already matched, or not interactive, do nothing.
if (self.matched || !self.interactive) {
return;
}
// If card is already revealed (face up), it means it was one of the selected cards.
// Clicking it again on 'up' should just restore its scale if it was pressed. No re-reveal.
if (self.revealed) {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
}); // Restore scale
return;
}
// At this point, card is face down, interactive, and not matched.
// We need to check again if we are allowed to reveal it. This is a final gate.
// The 'down' action might have scaled it down.
// If currentMemoryGame is null or canSelectCard is false, just restore scale and don't reveal.
if (!currentMemoryGame || !currentMemoryGame.canSelectCard()) {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
}); // Restore scale
return;
}
// Allowed to reveal. Animate scale back and then reveal.
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
onFinish: function onFinish() {
self.reveal(); // self.reveal() has its own checks too.
}
});
};
return self;
});
var MemoryGame = Container.expand(function () {
var self = Container.call(this);
// Game state variables
var selectedCards = [];
var matchedPairs = 0;
var totalPairs = 8;
var canSelect = true;
var movesMade = 0;
// Create cards array
var cards = [];
// Create semi-transparent background
var gameBackground = LK.getAsset('pet_playing', {
anchorX: 0.5,
anchorY: 0.5,
width: 2000,
height: 2000,
tint: 0x000000
});
gameBackground.alpha = 0.7;
self.addChild(gameBackground);
// Title text
var titleText = new Text2("Memory Game", {
size: 80,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.y = -900;
self.addChild(titleText);
// Moves counter
var movesText = new Text2("Moves: 0", {
size: 60,
fill: 0xFFFFFF
});
movesText.anchor.set(0.5, 0.5);
movesText.y = -800;
self.addChild(movesText);
// Setup the game
self.startGame = function () {
currentMemoryGame = self; // Set the global reference
// Reset game state
selectedCards = [];
matchedPairs = 0;
movesMade = 0;
canSelect = true;
// Create card values (pairs of 1-8)
var values = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8];
// Shuffle the array
for (var i = values.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = values[i];
values[i] = values[j];
values[j] = temp;
}
// Create and position cards in a 4x4 grid
var gridSize = 4;
// Create a temp card to measure actual size
var tempCard = new MemoryCard(1);
// Calculate spacing based on actual card dimensions
var cardWidth = tempCard.back.width;
var cardHeight = tempCard.back.height;
var cardSpacing = cardWidth * 1.2; // Add 20% of width as spacing
var verticalSpacing = cardHeight * 1.4; // Add 40% of height as vertical spacing
// Calculate starting coordinates using actual card dimensions
var startX = -((gridSize - 1) * cardSpacing) / 2;
var startY = -((gridSize - 1) * verticalSpacing) / 2;
// Clean up temp card
tempCard = null;
for (var row = 0; row < gridSize; row++) {
for (var col = 0; col < gridSize; col++) {
var index = row * gridSize + col;
var card = new MemoryCard(values[index]);
// Position card in grid using dynamic spacing based on card size
card.x = startX + col * cardSpacing;
card.y = startY + row * verticalSpacing;
// Set up reveal callback
card.onReveal = handleCardReveal;
// Add to game
self.addChild(card);
cards.push(card);
}
}
// Update moves display
updateMovesText();
// Fade in animation
self.alpha = 0;
tween(self, {
alpha: 1
}, {
duration: 500
});
};
// Handle card reveal
function handleCardReveal(card) {
if (!canSelect) {
return;
}
// Add card to selected cards
selectedCards.push(card);
// If we have 2 cards selected
if (selectedCards.length === 2) {
canSelect = false;
movesMade++;
updateMovesText();
// Check for match
if (selectedCards[0].value === selectedCards[1].value) {
// Match found!
selectedCards[0].setMatched();
selectedCards[1].setMatched();
matchedPairs++;
// Clear selection
selectedCards = [];
canSelect = true;
// Check if game is complete
if (matchedPairs === totalPairs) {
// Game won!
LK.setTimeout(function () {
endGame();
}, 500);
}
} else {
// No match, flip cards back after a delay
LK.setTimeout(function () {
selectedCards[0].hide();
selectedCards[1].hide();
selectedCards = [];
canSelect = true;
}, 1000);
}
}
}
// Update moves text
function updateMovesText() {
movesText.setText("Moves: " + movesMade);
}
self.canSelectCard = function () {
return canSelect;
};
// End the game
function endGame() {
// Calculate score (higher for fewer moves)
var baseScore = 100;
var movesPenalty = Math.min(80, movesMade * 2);
var finalScore = Math.max(20, baseScore - movesPenalty);
// Show winning message
var winText = new Text2("Memory Game Complete!\nScore: " + finalScore, {
size: 80,
fill: 0xFFFFFF,
align: "center"
});
winText.anchor.set(0.5, 0.5);
self.addChild(winText);
// Animate text
winText.scale.set(0);
tween(winText.scale, {
x: 1,
y: 1
}, {
duration: 500,
easing: tween.elasticOut
});
// Close after delay
LK.setTimeout(function () {
// Fade out animation
tween(self, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
if (self.onComplete) {
self.onComplete(finalScore);
}
currentMemoryGame = null; // Clear the global reference
// Remove all cards
for (var i = 0; i < cards.length; i++) {
self.removeChild(cards[i]);
}
cards = [];
// Remove win text
self.removeChild(winText);
// Remove self from parent
if (self.parent) {
self.parent.removeChild(self);
}
}
});
}, 2000);
}
// Close button
var closeButton = new Text2("X", {
size: 80,
fill: 0xFFFFFF
});
closeButton.anchor.set(0.5, 0.5);
closeButton.x = 900;
closeButton.y = -900;
closeButton.interactive = true;
self.addChild(closeButton);
closeButton.down = function () {
tween(closeButton, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 100
});
};
closeButton.up = function () {
tween(closeButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
onFinish: function onFinish() {
// Fade out and close
tween(self, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
// Calculate partial score
var partialScore = Math.max(5, matchedPairs * 10);
if (self.onComplete) {
self.onComplete(partialScore);
}
currentMemoryGame = null; // Clear the global reference
// Remove all cards
for (var i = 0; i < cards.length; i++) {
self.removeChild(cards[i]);
}
cards = [];
// Remove self from parent
if (self.parent) {
self.parent.removeChild(self);
}
}
});
}
});
};
// Center the game on screen
self.x = 2048 / 2;
self.y = 2732 / 2;
// Start the game
self.startGame();
return self;
});
var NameSelect = Container.expand(function (petType) {
var self = Container.call(this);
var petName = "";
self.petType = petType;
// Background setup
var background = self.attachAsset('menu_background', {
anchorX: 0.5,
anchorY: 0.5
});
background.x = 2048 / 2;
background.y = 2732 / 2;
// Title text
var titleText = new Text2("Name Your Pet", {
size: 100,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
titleText.y = 500;
self.addChild(titleText);
// Add selected pet preview
var petPreview = self.attachAsset(petType === 'Fluffy' ? 'pet_select_1' : 'pet_select_2', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 - 300
});
// Name input display
var nameDisplay = new Text2("_", {
size: 80,
fill: 0xFFFFFF
});
nameDisplay.anchor.set(0.5, 0.5);
nameDisplay.x = 2048 / 2;
nameDisplay.y = 2732 / 2 + 100;
self.addChild(nameDisplay);
// Instruction text
var instructionText = new Text2("Tap letters to name your pet", {
size: 60,
fill: 0xFFFFFF
});
instructionText.anchor.set(0.5, 0.5);
instructionText.x = 2048 / 2;
instructionText.y = 2732 / 2 + 200;
self.addChild(instructionText);
// Create keyboard layout
var keyboard = new Container();
keyboard.x = 2048 / 2;
keyboard.y = 2732 / 2 + 400;
self.addChild(keyboard);
// Define keyboard characters
var keys = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "⌫"];
// Create keyboard buttons
var keySize = 150;
var keysPerRow = 7;
var keySpacing = 15;
for (var i = 0; i < keys.length; i++) {
var row = Math.floor(i / keysPerRow);
var col = i % keysPerRow;
// Create key background
var keyBg = LK.getAsset('progressBar_bg', {
anchorX: 0.5,
anchorY: 0.5,
width: keySize,
height: keySize,
tint: keys[i] === "⌫" ? 0xFF6B6B : 0x70a1ff
});
// Create key text
var keyText = new Text2(keys[i], {
size: 60,
fill: 0xFFFFFF
});
keyText.anchor.set(0.5, 0.5);
// Create key container
var key = new Container();
key.addChild(keyBg);
key.addChild(keyText);
key.x = (col - keysPerRow / 2) * (keySize + keySpacing);
key.y = row * (keySize + keySpacing);
key.keyValue = keys[i];
key.interactive = true;
// Key interactions
key.down = function (x, y, obj) {
tween(this, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 100
});
};
key.up = function (x, y, obj) {
var keyValue = this.keyValue;
tween(this, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
onFinish: function onFinish() {
// Handle backspace
if (keyValue === "⌫") {
if (petName.length > 0) {
petName = petName.slice(0, -1);
}
}
// Add letter if name is less than 10 characters
else if (petName.length < 10) {
petName += keyValue;
}
// Update name display
nameDisplay.setText(petName + "_");
}
});
};
keyboard.addChild(key);
}
// Done button
var doneButton = new Container();
var doneBg = LK.getAsset('progressBar_bg', {
anchorX: 0.5,
anchorY: 0.5,
width: 300,
height: 100,
tint: 0x7bed9f
});
var doneText = new Text2("DONE", {
size: 70,
fill: 0xFFFFFF
});
doneText.anchor.set(0.5, 0.5);
doneButton.addChild(doneBg);
doneButton.addChild(doneText);
doneButton.x = 2048 / 2;
doneButton.y = 2732 / 2 + 1200;
doneButton.interactive = true;
self.addChild(doneButton);
doneButton.down = function (x, y, obj) {
tween(doneButton, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100
});
};
doneButton.up = function (x, y, obj) {
tween(doneButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
onFinish: function onFinish() {
// Use default name if empty
if (petName.length === 0) {
petName = self.petType;
}
if (self.onNameSelected) {
self.onNameSelected(petName);
}
}
});
};
return self;
});
var Pet = Container.expand(function () {
var self = Container.call(this);
var currentLevel = storage.petLevel || 1;
var petName = storage.petName || "Pet";
var petType = storage.petType || "Fluffy";
var petAssetId = 'pet_baby';
// Determine which asset to use based on level and pet type
if (petType === "Puffy") {
if (currentLevel === 1) {
petAssetId = 'pet_puffy_baby';
} else if (currentLevel === 2) {
petAssetId = 'pet_puffy_child';
} else if (currentLevel === 3) {
petAssetId = 'pet_puffy_teen';
} else if (currentLevel >= 4) {
petAssetId = 'pet_puffy_adult';
}
} else {
// Default for Fluffy pet
if (currentLevel === 1) {
petAssetId = 'pet_baby';
} else if (currentLevel === 2) {
petAssetId = 'pet_child';
} else if (currentLevel === 3) {
petAssetId = 'pet_teen';
} else if (currentLevel >= 4) {
petAssetId = 'pet_adult';
}
}
var petGraphics = self.attachAsset(petAssetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Add pet name display
var nameText = new Text2(petName, {
size: 50,
fill: 0xFFFFFF
});
nameText.anchor.set(0.5, 0.5);
nameText.y = -petGraphics.height / 2 - 40; // Initial position above pet
self.addChild(nameText);
// Method to reposition name to appear above level text
self.positionNameAboveLevel = function () {
// Remove from pet container
self.removeChild(nameText);
// Add directly to game container so it can be positioned independently
game.addChild(nameText);
// Position above level text
nameText.x = 2048 / 2;
nameText.y = 120; // Position above level text
};
self.happy = function () {
// Bounce animation
tween(self, {
y: self.y - 50
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
y: self.y + 50
}, {
duration: 300,
easing: tween.bounceOut,
onFinish: function onFinish() {
petPosition.x = self.x;
petPosition.y = self.y;
}
});
}
});
};
self.evolve = function () {
// Evolution animation
LK.getSound('evolve').play();
// Flash and grow
LK.effects.flashObject(self, 0xFFFFFF, 1000);
tween(petGraphics, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
// Update to new visual based on new level
currentLevel = storage.petLevel;
var newPetAssetId;
var petType = storage.petType || "Fluffy";
if (petType === "Puffy") {
if (currentLevel === 2) {
newPetAssetId = 'pet_puffy_child';
} else if (currentLevel === 3) {
newPetAssetId = 'pet_puffy_teen';
} else if (currentLevel >= 4) {
newPetAssetId = 'pet_puffy_adult';
}
} else {
// Default for Fluffy pet
if (currentLevel === 2) {
newPetAssetId = 'pet_child';
} else if (currentLevel === 3) {
newPetAssetId = 'pet_teen';
} else if (currentLevel >= 4) {
newPetAssetId = 'pet_adult';
}
}
// Remove old graphic and add new one
self.removeChild(petGraphics);
petGraphics = self.attachAsset(newPetAssetId, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
// Reset scale with animation
tween(petGraphics, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.easeOut
});
}
});
};
return self;
});
var Poop = Container.expand(function () {
var self = Container.call(this);
// Create poop visual using a single image
var poopGraphics = self.attachAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1,
scaleY: 1,
tint: 0x8B4513 // Brown color
});
// Add appear animation
self.appear = function () {
self.scale.set(0);
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.bounceOut
});
};
// Add cleanup animation
self.cleanup = function (callback) {
tween(self, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 300,
easing: tween.easeInBack,
onFinish: function onFinish() {
if (callback) {
callback();
}
}
});
};
return self;
});
var StatusText = Text2.expand(function (initialText) {
var self = Text2.call(this, initialText, {
size: 60,
fill: 0xFFFFFF
});
self.anchor.set(0.5, 0.5);
self.showMessage = function (message) {
self.setText(message);
self.alpha = 1;
tween(self, {
alpha: 0
}, {
duration: 3000,
easing: tween.linear
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x7ED6DF
});
/****
* Game Code
****/
// Game state - load these first
var petLevel = storage.petLevel || 1;
var petXp = storage.petXp || 0;
var lastFed = storage.lastFed || 0;
var lastCleaned = storage.lastCleaned || 0;
var lastPlayed = storage.lastPlayed || 0;
// Load pet state flags from storage
var isHungry = false;
var isBored = false;
var isCheerNeeded = false;
var hasActivePoop = false;
var canFeedPet = false;
var canPlayPet = false;
var canCheerPet = false;
var canCleanPet = false;
var speechBubble; // Variable for the speech bubble
// Timers and intervals for needs
var needTimer = null;
var needTimeout = null;
var hungerTimer = null; // Added to fix 'hungerTimer is not defined'
var boredTimer = null; // Added to fix 'boredTimer is not defined'
var cheerXpLossTimer = null; // Timer for XP loss when cheer is ignored
var XP_LOSS_TIMEOUT = 60000; // 1 minute
var POOP_SPEECH_TIMEOUT = 30000; // 30 seconds
var POOP_XP_LOSS_TIMEOUT = 60000; // 1 minute
var BORED_XP_LOSS_TIMEOUT = 90000; // 1.5 minutes
var XP_LOSS_INTERVAL = 90000; // 1.5 minutes interval for XP loss if player doesn't play with pet
var xpLossTimer; // Timer for XP loss after not feeding
var XP_LOSS_AMOUNT = 50; // Amount of XP to lose
var poopTimer; // Timer for when pet will poop
var activePoop = null; // Reference to the active poop object
var poopSpeechTimer; // Timer for poop speech bubble
var poopXpLossTimer; // Timer for XP loss from uncleaned poop
var boredXpLossTimer; // Timer for XP loss when bored
// Define game constants - calculate after loading state
var BASE_XP_PER_LEVEL = 200;
var XP_PER_LEVEL = BASE_XP_PER_LEVEL * Math.pow(2, petLevel - 1); // Doubles with each level
var XP_GAIN_FEED = 20;
var XP_GAIN_CLEAN = 15;
var XP_GAIN_PLAY = 25;
var COOLDOWN_TIME = 10000; // 10 seconds cooldown between actions
var PLAY_INACTIVITY_XP_LOSS = 15; // Amount of XP to lose from play inactivity
var playInactivityInterval; // Interval for recurring XP loss due to play inactivity
var hpText; // Define hpText in global scope
var barFill; // Define barFill in global scope
var barBackground; // Define barBackground in global scope
// Track which need is currently active
var currentNeed = null; // "hunger", "boredom", "poop", "cheer"
// New: For random need logic
var NEEDS = ["hunger", "boredom", "poop", "cheer"];
// Vector2 positions for pet at different evolution levels
var petBabyPosition = {
x: 2048 / 2,
y: 2732 / 2 + 250
};
var petChildPosition = {
x: 2048 / 2,
y: 2732 / 2 + 200
};
var petTeenPosition = {
x: 2048 / 2,
y: 2732 / 2 + 150
};
var petAdultPosition = {
x: 2048 / 2,
y: 2732 / 2 + 150
};
// Track current game state
var progressBar = new Container();
var currentScreen = "menu";
var mainMenu, pet, progressBar, statusText, levelText;
var currentMemoryGame = null; // To hold the active MemoryGame instance
var isMemoryGameActive = false; // Track if memory game is active
var feedButton, cleanButton, playButton;
var progressBarContainer = new Container();
// Current pet position based on level
var petPosition = petBabyPosition;
// Initialize menu
function initMainMenu() {
LK.playMusic('bgmusic');
// Clear previous game elements if they exist
clearGameElements();
// Always show main menu first
mainMenu = new MainMenu();
game.addChild(mainMenu);
// Set up callbacks
setupMainMenuCallbacks();
// Function to set up main menu callbacks
function setupMainMenuCallbacks() {
// Set up start game callback
mainMenu.onStartGame = function () {
// Check if it's first time playing or reset game
if (!storage.petName) {
// First time playing, transition to character select
tween(mainMenu, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
game.removeChild(mainMenu);
// Show character select screen
var characterSelect = new CharacterSelect();
game.addChild(characterSelect);
// Handle character selection
characterSelect.onSelectCharacter = function (selectedPetType) {
// Show name selection screen
tween(characterSelect, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
game.removeChild(characterSelect);
// Create name selection screen
var nameSelect = new NameSelect(selectedPetType);
game.addChild(nameSelect);
// Handle name selection
nameSelect.onNameSelected = function (selectedName) {
// Save the selected name
storage.petName = selectedName;
// Hide name select and start game
tween(nameSelect, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
game.removeChild(nameSelect);
// Now start the game
currentScreen = "game";
initGameScreen();
}
});
};
}
});
};
}
});
} else {
// Returning player with saved data, go directly to gameplay
currentScreen = "game";
initGameScreen();
}
};
// Set up reset game callback
mainMenu.onResetGame = function () {
// Reset all storage values
storage.petLevel = 1;
petLevel = 1; // Also reset the global petLevel variable
storage.petXp = 0;
petXp = 0; // Reset the current petXp value
storage.lastFed = 0;
storage.lastCleaned = 0;
storage.lastPlayed = 0;
storage.isHungry = false;
storage.isBored = false;
storage.hasActivePoop = false;
storage.petName = null; // Reset pet name to trigger character select again
// Show confirmation text
var confirmText = new Text2("Game data reset!", {
size: 60,
fill: 0xFFFFFF
});
confirmText.anchor.set(0.5, 0.5);
confirmText.x = 2048 / 2;
confirmText.y = 2732 / 2 + 500;
mainMenu.addChild(confirmText);
// Fade out confirmation text
tween(confirmText, {
alpha: 0
}, {
duration: 2000,
onFinish: function onFinish() {
mainMenu.removeChild(confirmText);
}
});
};
}
}
// This function is now defined inside initMainMenu
// Initialize game screen
function initGameScreen() {
console.log("Initializing game screen");
// Clear previous elements
clearGameElements();
// Add game background
var gameBackground = LK.getAsset('game_background', {
anchorX: 0.5,
anchorY: 0.5
});
gameBackground.x = 2048 / 2;
gameBackground.y = 2732 / 2;
game.addChild(gameBackground);
// Create game elements
pet = new Pet();
pet.x = petPosition.x;
pet.y = petPosition.y;
game.addChild(pet);
// Position pet name above level text
pet.positionNameAboveLevel();
// Add mechanic: show image above and left of pet when clicked, and allow XP gain on image click every 5s
var petClickImage = null;
var lastPetClickImageXpTime = 0; // Track last time XP was given from image
pet.interactive = true;
pet.down = function (x, y, obj) {
// Remove previous image if exists
if (petClickImage && petClickImage.parent) {
petClickImage.parent.removeChild(petClickImage);
petClickImage = null;
}
// Create the image (now using 'cheer_image' for cheering)
petClickImage = LK.getAsset('cheer_image', {
anchorX: 0.5,
anchorY: 0.5,
width: 400,
height: 400
});
// Position above and left of pet
petClickImage.x = pet.x - pet.width / 2 - 100;
petClickImage.y = pet.y - pet.height / 2 - 100;
game.addChild(petClickImage);
// Animate in (optional)
petClickImage.alpha = 0;
tween(petClickImage, {
alpha: 1
}, {
duration: 200
});
// Add interaction to the image for XP gain
petClickImage.interactive = true;
petClickImage.down = function (x2, y2, obj2) {
// Visual feedback for press
tween(petClickImage, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 80
});
};
petClickImage.up = function (x2, y2, obj2) {
// Prevent cheering if memory game is active
if (isMemoryGameActive) {
statusText.showMessage("Finish the memory game first!");
return;
}
// Restore scale
tween(petClickImage, {
scaleX: 1,
scaleY: 1
}, {
duration: 80
});
var now = Date.now();
if (canCheerPet && isCheerNeeded) {
// Fulfill cheer need
addXp(20);
statusText.showMessage("Pet cheered! +20 XP");
canCheerPet = false;
isCheerNeeded = false;
// Hide cheer speech bubble if visible and it's showing the cheer message
if (speechBubble && speechBubble.alpha > 0 && (speechBubble.text === "Pet me! \nI need a cheer!" || speechBubble.text === "I feel lonely! \nPet me please!")) {
LK.setTimeout(function () {
speechBubble.hide(300);
}, 3000);
}
// Schedule next need after cheer is fulfilled
game.scheduleNextNeed();
// Clear cheer XP loss timer
if (cheerXpLossTimer) {
LK.clearTimeout(cheerXpLossTimer);
cheerXpLossTimer = null;
}
// Pet reacts (bounce/happy animation)
if (pet && typeof pet.happy === "function") {
pet.happy();
}
lastPetClickImageXpTime = now;
} else if (!lastPetClickImageXpTime || now - lastPetClickImageXpTime >= 5000) {
addXp(15);
statusText.showMessage("Pet is happy! +15 XP");
lastPetClickImageXpTime = now;
// Pet reacts (bounce/happy animation)
if (pet && typeof pet.happy === "function") {
pet.happy();
}
} else {
statusText.showMessage("Wait before cheering again!");
}
};
// Hide after 1.5 seconds
LK.setTimeout(function () {
if (petClickImage && petClickImage.parent) {
tween(petClickImage, {
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
if (petClickImage && petClickImage.parent) {
petClickImage.parent.removeChild(petClickImage);
petClickImage = null;
}
}
});
}
}, 1500);
};
// Level text
levelText = new Text2("Level " + petLevel, {
size: 80,
fill: 0xFFFFFF
});
levelText.anchor.set(0.5, 0.5);
levelText.x = 2048 / 2;
levelText.y = 200;
game.addChild(levelText);
// Create action buttons
feedButton = new ActionButton('feed');
feedButton.x = 2048 / 4;
feedButton.y = 2732 - 400;
game.addChild(feedButton);
cleanButton = new ActionButton('clean');
cleanButton.x = 2048 / 2;
cleanButton.y = 2732 - 400;
game.addChild(cleanButton);
playButton = new ActionButton('play');
playButton.x = 2048 * 3 / 4;
playButton.y = 2732 - 400;
game.addChild(playButton);
// Recalculate XP_PER_LEVEL based on current level
XP_PER_LEVEL = BASE_XP_PER_LEVEL * Math.pow(2, petLevel - 1);
console.log("Current petLevel:", petLevel, "XP_PER_LEVEL:", XP_PER_LEVEL);
// Make sure all values are calculated before updating progress bar
XP_PER_LEVEL = BASE_XP_PER_LEVEL * Math.pow(2, petLevel - 1);
// Update the progress bar on start
console.log("Initial progress bar update with petXp:", petXp, "XP_PER_LEVEL:", XP_PER_LEVEL);
// Make sure variables are initialized properly
petXp = petXp || 0;
XP_PER_LEVEL = XP_PER_LEVEL || BASE_XP_PER_LEVEL;
// Now create HP text after progress bar exists
hpText = new Text2("XP: " + petXp + "/" + XP_PER_LEVEL, {
size: 60,
fill: 0xFFFFFF
});
hpText.anchor.set(0, 0.5);
hpText.x = progressBar.x + 10;
hpText.y = progressBar.y - 50;
game.addChild(hpText);
// Create HP text display
console.log("Creating HP text with petXp:", petXp, "XP_PER_LEVEL:", XP_PER_LEVEL);
hpText = new Text2("XP: " + petXp + "/" + XP_PER_LEVEL, {
size: 60,
fill: 0xFFFFFF
});
hpText.anchor.set(0, 0.5);
game.addChild(hpText);
console.log("Game screen initialization complete");
createProgressBar();
addBarFillXp();
// Status text
statusText = new StatusText("");
statusText.x = 2048 / 2;
statusText.y = progressBar.y + 100;
statusText.alpha = 0;
game.addChild(statusText);
// Create speech bubble for needs
speechBubble = new BubbleSpeech("");
speechBubble.x = pet.x + 450;
speechBubble.y = pet.y - 450; // Position above pet
speechBubble.alpha = 0; // Start invisible
game.addChild(speechBubble);
// Helper: clear any need timers
function clearNeedTimers() {
if (needTimer) {
LK.clearTimeout(needTimer);
needTimer = null;
}
if (needTimeout) {
LK.clearTimeout(needTimeout);
needTimeout = null;
}
if (xpLossTimer) {
LK.clearTimeout(xpLossTimer);
xpLossTimer = null;
}
if (boredXpLossTimer) {
LK.clearTimeout(boredXpLossTimer);
boredXpLossTimer = null;
}
if (playInactivityInterval) {
LK.clearInterval(playInactivityInterval);
playInactivityInterval = null;
}
if (poopSpeechTimer) {
LK.clearTimeout(poopSpeechTimer);
poopSpeechTimer = null;
}
if (poopXpLossTimer) {
LK.clearTimeout(poopXpLossTimer);
poopXpLossTimer = null;
}
if (cheerXpLossTimer) {
LK.clearTimeout(cheerXpLossTimer);
cheerXpLossTimer = null;
}
canCheerPet = false;
isCheerNeeded = false;
}
// Helper: pick a random need
function pickRandomNeed() {
// If there is active poop, always return "poop"
if (activePoop) {
return "poop";
}
// Increase probability of 'boredom' by weighting the array
// e.g. ["hunger", "boredom", "boredom", "boredom", "cheer"]
var weightedNeeds = ["hunger", "boredom", "boredom", "boredom", "cheer"];
var idx = Math.floor(Math.random() * weightedNeeds.length);
return weightedNeeds[idx];
}
// Helper: schedule the next random need after a random delay (15-30s)
function scheduleNextNeed() {
var nextDelay = 15000 + Math.floor(Math.random() * 15000); // 15-30s
needTimer = LK.setTimeout(function () {
showRandomNeed();
}, nextDelay);
}
// Assign to game so it can be called as game.scheduleNextNeed()
game.scheduleNextNeed = scheduleNextNeed;
// Main: show a random need
function showRandomNeed() {
// If memory game is active, wait and try again later
if (isMemoryGameActive) {
// Try again in 2 seconds
needTimer = LK.setTimeout(function () {
showRandomNeed();
}, 2000);
return;
}
clearNeedTimers();
// Pick a random need
var need = pickRandomNeed();
currentNeed = need;
canFeedPet = true;
canPlayPet = false;
canCleanPet = false;
isHungry = false;
isBored = false;
// Show the bubble and set the action variable
if (need === "hunger") {
speechBubble.setText("I'm hungry!");
speechBubble.show(500);
canFeedPet = true;
isHungry = true;
// XP loss if ignored
xpLossTimer = LK.setTimeout(function () {
if (petXp > 0) {
addXp(-XP_LOSS_AMOUNT);
statusText.showMessage("Pet is hungry! Lost " + XP_LOSS_AMOUNT + " XP!");
}
}, XP_LOSS_TIMEOUT);
} else if (need === "boredom") {
speechBubble.setText("I'm bored! \nPlay with me!");
speechBubble.show(500);
canPlayPet = true;
isBored = true;
// XP loss if ignored
boredXpLossTimer = LK.setTimeout(function () {
if (petXp > 0) {
addXp(-XP_LOSS_AMOUNT);
statusText.showMessage("Pet is bored! Lost " + XP_LOSS_AMOUNT + " XP!");
}
}, BORED_XP_LOSS_TIMEOUT);
// Play inactivity XP loss
playInactivityInterval = LK.setInterval(function () {
var currentTime = Date.now();
var timeSinceLastPlayed = currentTime - lastPlayed;
if (timeSinceLastPlayed > 300000 && petXp > 0) {
addXp(-PLAY_INACTIVITY_XP_LOSS);
statusText.showMessage("Your pet misses you! Lost " + PLAY_INACTIVITY_XP_LOSS + " XP!");
speechBubble.setText("I miss \nplaying \nwith you!");
speechBubble.show(500);
LK.setTimeout(function () {
speechBubble.hide(300);
}, 3000);
}
}, XP_LOSS_INTERVAL);
} else if (need === "cheer") {
speechBubble.setText("Pet me! \nI need a cheer!");
speechBubble.show(500);
canCheerPet = true;
isCheerNeeded = true;
// XP loss if ignored
cheerXpLossTimer = LK.setTimeout(function () {
if (petXp > 0) {
addXp(-XP_LOSS_AMOUNT);
statusText.showMessage("Pet feels lonely! Lost " + XP_LOSS_AMOUNT + " XP!");
speechBubble.setText("I feel lonely! \nPet me please!");
speechBubble.show(500);
LK.setTimeout(function () {
speechBubble.hide(300);
}, 3000);
}
}, BORED_XP_LOSS_TIMEOUT);
} else if (need === "poop") {
// If no active poop, spawn one
if (!activePoop) {
activePoop = new Poop();
activePoop.x = pet.x + 900;
activePoop.y = pet.y + 200;
game.addChild(activePoop);
activePoop.appear();
// Set canCleanPet to true and enable cleanButton when poop spawns
canCleanPet = true;
if (cleanButton) {
cleanButton.interactive = true;
}
}
speechBubble.setText("Please clean \nthis mess!");
speechBubble.show(500);
// Set canCleanPet to true when poop and clean speech bubble appear
canCleanPet = true;
// XP loss if ignored
poopXpLossTimer = LK.setTimeout(function () {
var loseXpInterval = LK.setInterval(function () {
if (activePoop) {
if (petXp > 0) {
addXp(-XP_LOSS_AMOUNT);
statusText.showMessage("Pet uncomfortable! Lost " + XP_LOSS_AMOUNT + " XP!");
speechBubble.setText("This mess \nis making \nme lose XP!");
speechBubble.show(500);
LK.setTimeout(function () {
speechBubble.hide(300);
}, 3000);
}
} else {
LK.clearInterval(loseXpInterval);
}
}, POOP_XP_LOSS_TIMEOUT);
}, POOP_XP_LOSS_TIMEOUT);
}
// Hide every speech bubble after 3 seconds
LK.setTimeout(function () {
speechBubble.hide(300);
}, 3000);
}
// If there was an active poop, restore it
if (activePoop) {
activePoop = new Poop();
activePoop.x = pet.x + Math.random() * 1000;
activePoop.y = pet.y + 100 + Math.random() * 50;
game.addChild(activePoop);
activePoop.appear();
}
// Start the random need loop
showRandomNeed();
} //[3J]
// Clear all game elements
// Function to stop a specific timer by name
function stopTimer(timerName) {
// Check if timer name is valid before attempting to stop
if (!timerName) {
console.log("Cannot stop timer: timer name is undefined or null");
return;
}
if (timerName === 'hunger' && hungerTimer) {
LK.clearTimeout(hungerTimer);
hungerTimer = null;
console.log("Hunger timer stopped");
} else if (timerName === 'xpLoss' && xpLossTimer) {
LK.clearTimeout(xpLossTimer);
xpLossTimer = null;
console.log("XP loss timer stopped");
} else if (timerName === 'poop' && poopTimer) {
LK.clearTimeout(poopTimer);
poopTimer = null;
console.log("Poop timer stopped");
} else if (timerName === 'poopSpeech' && poopSpeechTimer) {
LK.clearTimeout(poopSpeechTimer);
poopSpeechTimer = null;
console.log("Poop speech timer stopped");
} else if (timerName === 'poopXpLoss' && poopXpLossTimer) {
LK.clearTimeout(poopXpLossTimer);
poopXpLossTimer = null;
console.log("Poop XP loss timer stopped");
} else if (timerName === 'bored' && boredTimer) {
LK.clearTimeout(boredTimer);
boredTimer = null;
console.log("Bored timer stopped");
} else if (timerName === 'boredXpLoss' && boredXpLossTimer) {
LK.clearTimeout(boredXpLossTimer);
boredXpLossTimer = null;
console.log("Bored XP loss timer stopped");
} else if (timerName === 'playInactivity' && playInactivityInterval) {
LK.clearInterval(playInactivityInterval);
playInactivityInterval = null;
console.log("Play inactivity interval stopped");
} else {
console.log("Timer not found or already stopped: " + timerName);
}
}
function clearGameElements() {
// Clear hunger timer if exists
if (hungerTimer) {
LK.clearTimeout(hungerTimer);
hungerTimer = null;
}
// Clear hunger speech bubble interval if exists
if (typeof hungerSpeechBubbleInterval !== "undefined" && hungerSpeechBubbleInterval) {
LK.clearInterval(hungerSpeechBubbleInterval);
hungerSpeechBubbleInterval = null;
}
// Clear XP loss timer if exists
if (xpLossTimer) {
LK.clearTimeout(xpLossTimer);
xpLossTimer = null;
}
// Clear poop timer if exists
if (poopTimer) {
LK.clearTimeout(poopTimer);
poopTimer = null;
}
// Clear poop speech timer if exists
if (poopSpeechTimer) {
LK.clearTimeout(poopSpeechTimer);
poopSpeechTimer = null;
}
// Clear poop XP loss timer if exists
if (poopXpLossTimer) {
LK.clearTimeout(poopXpLossTimer);
poopXpLossTimer = null;
}
// Clear bored timer if exists
if (boredTimer) {
LK.clearTimeout(boredTimer);
boredTimer = null;
}
// Clear bored XP loss timer if exists
if (boredXpLossTimer) {
LK.clearTimeout(boredXpLossTimer);
boredXpLossTimer = null;
}
// Clear play inactivity interval if exists
if (playInactivityInterval) {
LK.clearInterval(playInactivityInterval);
playInactivityInterval = null;
}
// Clear cheer XP loss timer if exists
if (cheerXpLossTimer) {
LK.clearTimeout(cheerXpLossTimer);
cheerXpLossTimer = null;
}
canCheerPet = false;
isCheerNeeded = false;
// Reset active poop reference
activePoop = null;
while (game.children.length > 0) {
game.removeChild(game.children[0]);
} //[3K]
} //[3L]
// Start with main menu
initMainMenu();
function performFeedAction() {
console.log("performFeedAction called");
var currentTime = Date.now(); //[3O]
// Check if pet is recently fed (on cooldown)
if (currentTime - lastFed < COOLDOWN_TIME) {
statusText.showMessage("Pet is still full! Wait a moment.");
console.log("Action on cooldown, can't feed yet");
return; //[3P]
} //[3Q]
// Check if feeding is allowed using canFeedPet variable
if (!canFeedPet) {
statusText.showMessage("Pet isn't hungry yet!");
console.log("Pet can't be fed yet - canFeedPet is false");
return;
}
// If hunger message is showing, we can feed the pet
if (speechBubble && speechBubble.alpha > 0 && speechBubble.text === "I'm hungry!") {
// Pet is hungry with message showing, continue with feeding
} else if (!canFeedPet) {
statusText.showMessage("Pet isn't hungry yet!");
console.log("Pet isn't hungry yet");
return;
}
console.log("Feeding pet, adding XP:", XP_GAIN_FEED);
LK.getSound('feed').play();
pet.happy(); //[3R]
lastFed = currentTime;
storage.lastFed = lastFed;
addXp(XP_GAIN_FEED);
console.log("After addXp call");
statusText.showMessage("Pet fed! +20 XP");
// Set canFeedPet to false after feeding
canFeedPet = false;
// Update hunger state in storage
storage.isHungry = false;
// Hide hunger speech bubble if visible and it's showing the hunger message
if (speechBubble && speechBubble.alpha > 0 && speechBubble.text === "I'm hungry!") {
LK.setTimeout(function () {
speechBubble.hide(300);
}, 3000);
}
// Schedule next need after feeding is resolved
game.scheduleNextNeed();
// Clear the repeated hunger speech bubble interval if it exists
if (typeof hungerSpeechBubbleInterval !== "undefined" && hungerSpeechBubbleInterval) {
LK.clearInterval(hungerSpeechBubbleInterval);
hungerSpeechBubbleInterval = null;
}
// Reset hunger timer
if (hungerTimer) {
LK.clearTimeout(hungerTimer);
}
hungerTimer = LK.setTimeout(function () {
// Show speech bubble with animation
speechBubble.show(500);
// Set canFeedPet to true when hunger message appears again
canFeedPet = true;
// Update hunger state in storage
storage.isHungry = true;
console.log("Pet is hungry again - canFeedPet set to true");
}, 60000);
// Reset XP loss timer when feeding
if (xpLossTimer) {
LK.clearTimeout(xpLossTimer);
}
xpLossTimer = LK.setTimeout(function () {
if (petXp > 0) {
addXp(-XP_LOSS_AMOUNT);
statusText.showMessage("Pet is hungry! Lost " + XP_LOSS_AMOUNT + " XP!");
}
}, XP_LOSS_TIMEOUT);
// Set timer for pet to poop 15 seconds after being fed every time
if (poopTimer) {
LK.clearTimeout(poopTimer);
}
poopTimer = LK.setTimeout(function () {
// Only create poop if there isn't one already
if (!activePoop) {
// Create new poop slightly behind the pet
activePoop = new Poop();
activePoop.x = pet.x + 1000; // Random position near pet
activePoop.y = pet.y + 200;
game.addChild(activePoop);
activePoop.appear();
// Update poop state in storage
storage.hasActivePoop = true;
// Create a speech bubble for the poop event
speechBubble.setText("I made \na mess!");
speechBubble.show(500);
// Hide speech bubble after 3 seconds
LK.setTimeout(function () {
speechBubble.hide(300);
}, 3000);
// Set timer to show clean up reminder after 1 minute
if (poopSpeechTimer) {
LK.clearTimeout(poopSpeechTimer);
}
poopSpeechTimer = LK.setTimeout(function () {
speechBubble.setText("Please clean \nthis mess!");
speechBubble.show(500);
}, POOP_SPEECH_TIMEOUT);
// Set timer for XP loss after 2 minutes of not cleaning
if (poopXpLossTimer) {
LK.clearTimeout(poopXpLossTimer);
}
poopXpLossTimer = LK.setTimeout(function () {
// Set up recurring XP loss every 2 minutes until cleaned
var loseXpInterval = LK.setInterval(function () {
if (activePoop) {
if (petXp > 0) {
addXp(-XP_LOSS_AMOUNT);
statusText.showMessage("Pet uncomfortable! Lost " + XP_LOSS_AMOUNT + " XP!");
// Show speech bubble about being uncomfortable
speechBubble.setText("This mess \nis making \nme lose XP!");
speechBubble.show(500);
LK.setTimeout(function () {
speechBubble.hide(300);
}, 3000);
}
} else {
// Clear interval if poop is gone
LK.clearInterval(loseXpInterval);
}
}, POOP_XP_LOSS_TIMEOUT);
}, POOP_XP_LOSS_TIMEOUT);
}
}, 15000); // Always 15 seconds after eating
} //[3S]
function performCleanAction() {
var currentTime = Date.now();
if (currentTime - lastCleaned < COOLDOWN_TIME) {
statusText.showMessage("Pet is already clean! Wait a moment.");
return;
}
// Only allow cleaning if canCleanPet is true
if (!canCleanPet) {
statusText.showMessage("Pet doesn't need cleaning right now!");
return;
}
// Check if there's poop to clean
if (activePoop) {
LK.getSound('clean').play();
pet.happy();
lastCleaned = currentTime;
storage.lastCleaned = lastCleaned;
// Add extra XP for cleaning poop
addXp(XP_GAIN_CLEAN * 2);
statusText.showMessage("Poop cleaned! +30 XP");
// Animate poop cleanup
activePoop.cleanup(function () {
if (activePoop && activePoop.parent) {
activePoop.parent.removeChild(activePoop);
}
// Clear poop speech bubble timer
if (poopSpeechTimer) {
LK.clearTimeout(poopSpeechTimer);
poopSpeechTimer = null;
}
// Clear poop XP loss timer
if (poopXpLossTimer) {
LK.clearTimeout(poopXpLossTimer);
poopXpLossTimer = null;
}
activePoop = null;
// Update poop state in storage
storage.hasActivePoop = false;
canCleanPet = false; // Reset after cleaning
// Schedule next need after cleaning is resolved
game.scheduleNextNeed();
});
// Pet is happy about cleaning
LK.setTimeout(function () {
// Save previous speech bubble text so we know if it was about cleaning
var previousText = speechBubble.text;
speechBubble.setText("Thank you!");
speechBubble.show(500);
// Hide speech bubble after 3 seconds, but only if it was about cleaning
LK.setTimeout(function () {
// Only hide if we had shown the thank you message from cleaning
if (speechBubble.text === "Thank you!" && (previousText === "Please clean \nthis mess!" || previousText === "I made \na mess!" || previousText === "This mess \nis making \nme lose XP!")) {
speechBubble.hide(300);
}
}, 3000);
}, 500);
} else {
// Don't allow cleaning if there's no poop
statusText.showMessage("Nothing to clean right now!");
}
}
function performPlayAction() {
var currentTime = Date.now();
if (currentTime - lastPlayed < COOLDOWN_TIME) {
statusText.showMessage("Pet is tired! Wait a moment.");
return;
}
// Check if playing is allowed using canPlayPet variable
if (!canPlayPet) {
statusText.showMessage("Pet isn't bored yet!");
console.log("Pet can't be played with yet - canPlayPet is false");
return;
}
// Decide if memory game should appear (2 out of 3 times)
if (typeof playMemoryGameCounter === "undefined") {
playMemoryGameCounter = 0;
}
playMemoryGameCounter++;
var playMemoryGame = false;
if (playMemoryGameCounter >= 3) {
playMemoryGameCounter = 1;
}
if (playMemoryGameCounter === 1 || playMemoryGameCounter === 2) {
playMemoryGame = true;
}
LK.getSound('play').play();
pet.happy();
lastPlayed = currentTime;
storage.lastPlayed = lastPlayed;
if (playMemoryGame) {
// Launch memory game for extra XP
speechBubble.setText("Let's play a \nmemory \ngame!");
speechBubble.show(500);
// Pause all timers before starting memory game
var savedTimers = {
hungerTimer: hungerTimer,
xpLossTimer: xpLossTimer,
boredTimer: boredTimer,
boredXpLossTimer: boredXpLossTimer,
poopTimer: poopTimer,
poopSpeechTimer: poopSpeechTimer,
poopXpLossTimer: poopXpLossTimer
};
// Clear all active timers
if (hungerTimer) {
LK.clearTimeout(hungerTimer);
}
if (xpLossTimer) {
LK.clearTimeout(xpLossTimer);
}
if (boredTimer) {
LK.clearTimeout(boredTimer);
}
if (boredXpLossTimer) {
LK.clearTimeout(boredXpLossTimer);
}
if (poopTimer) {
LK.clearTimeout(poopTimer);
}
if (poopSpeechTimer) {
LK.clearTimeout(poopSpeechTimer);
}
if (poopXpLossTimer) {
LK.clearTimeout(poopXpLossTimer);
}
if (playInactivityInterval) {
LK.clearInterval(playInactivityInterval);
}
// Disable action buttons during memory game
feedButton.interactive = false;
cleanButton.interactive = false;
playButton.interactive = false;
isMemoryGameActive = true;
var memoryGame = new MemoryGame();
// Add delay before creating and showing memory game
LK.setTimeout(function () {
// Create and start memory game
game.addChild(memoryGame);
}, 1500); // 0.5 second delay
// Handle game completion
memoryGame.onComplete = function (score) {
isMemoryGameActive = false;
// Re-enable action buttons when game completes
feedButton.interactive = true;
cleanButton.interactive = true;
playButton.interactive = true;
// Only add XP if the player completes the memory game with a score of at least 50
if (score >= 50) {
// Add XP based on score (higher score = more XP)
var earnedXp = XP_GAIN_PLAY + score;
addXp(earnedXp);
statusText.showMessage("Beat memory game! +" + earnedXp + " XP");
// Tell player how they did
speechBubble.setText("Great job! \nYou earned \n" + earnedXp + " XP!");
// Pet is happy after successful game completion
pet.happy();
speechBubble.show(500);
// Hide speech bubble after 2 seconds, but only if it's related to boredom
LK.setTimeout(function () {
if (speechBubble && speechBubble.text && speechBubble.text.includes("Great job!")) {
speechBubble.hide(300);
}
}, 2000);
// Set canPlayPet to false after playing
canPlayPet = false;
// Update boredom state in storage
storage.isBored = false;
// Stop the boredXpLoss timer when winning the memory game
if (boredXpLossTimer) {
LK.clearTimeout(boredXpLossTimer);
boredXpLossTimer = null;
}
// Reset boredom timer
if (boredTimer) {
LK.clearTimeout(boredTimer);
}
boredTimer = LK.setTimeout(function () {
speechBubble.setText("I'm bored! \n Play with me!");
speechBubble.show(500);
canPlayPet = true;
storage.isBored = true;
console.log("Pet is bored again - canPlayPet set to true");
}, 60000);
if (boredTimer) {
LK.clearTimeout(boredTimer);
}
boredTimer = LK.setTimeout(function () {
speechBubble.setText("I'm bored! \n Play with me!");
speechBubble.show(500);
canPlayPet = true;
storage.isBored = true;
}, 60000);
if (xpLossTimer) {
LK.clearTimeout(xpLossTimer);
}
xpLossTimer = LK.setTimeout(function () {
if (petXp > 0) {
addXp(-XP_LOSS_AMOUNT);
statusText.showMessage("Pet is hungry! Lost " + XP_LOSS_AMOUNT + " XP!");
}
}, XP_LOSS_TIMEOUT);
} else {
// Player quit or didn't complete the game
statusText.showMessage("You must \ncomplete \nthe game!");
speechBubble.setText("Let's try \nagain!");
speechBubble.show(500);
// Wait a moment and then start a new memory game
LK.setTimeout(function () {
var newMemoryGame = new MemoryGame();
game.addChild(newMemoryGame);
newMemoryGame.onComplete = memoryGame.onComplete;
}, 2000);
return;
}
};
// Reset bored XP loss timer
if (boredXpLossTimer) {
LK.clearTimeout(boredXpLossTimer);
}
boredXpLossTimer = LK.setTimeout(function () {
if (petXp > 0) {
addXp(-XP_LOSS_AMOUNT);
statusText.showMessage("Pet is bored! Lost " + XP_LOSS_AMOUNT + " XP!");
}
}, BORED_XP_LOSS_TIMEOUT);
// Reset play inactivity interval to prevent additional XP loss
if (playInactivityInterval) {
LK.clearInterval(playInactivityInterval);
}
playInactivityInterval = LK.setInterval(function () {
var currentTime = Date.now();
var timeSinceLastPlayed = currentTime - lastPlayed;
if (timeSinceLastPlayed > 300000 && petXp > 0) {
addXp(-PLAY_INACTIVITY_XP_LOSS);
statusText.showMessage("Your pet misses you! Lost " + PLAY_INACTIVITY_XP_LOSS + " XP!");
speechBubble.setText("I miss playing `\n with you!");
speechBubble.show(500);
LK.setTimeout(function () {
speechBubble.hide(300);
}, 3000);
}
}, XP_LOSS_INTERVAL);
return;
}
// If not memory game, just play and give normal XP
addXp(XP_GAIN_PLAY);
statusText.showMessage("Played with pet! +" + XP_GAIN_PLAY + " XP");
speechBubble.setText("Yay! \nThat was fun!");
speechBubble.show(500);
LK.setTimeout(function () {
speechBubble.hide(300);
}, 3000);
canPlayPet = false;
storage.isBored = false;
if (boredXpLossTimer) {
LK.clearTimeout(boredXpLossTimer);
boredXpLossTimer = null;
}
if (boredTimer) {
LK.clearTimeout(boredTimer);
}
boredTimer = LK.setTimeout(function () {
speechBubble.setText("I'm bored! \n Play with me!");
speechBubble.show(500);
canPlayPet = true;
storage.isBored = true;
}, 60000);
if (boredTimer) {
LK.clearTimeout(boredTimer);
}
boredTimer = LK.setTimeout(function () {
speechBubble.setText("I'm bored! \n Play with me!");
speechBubble.show(500);
canPlayPet = true;
storage.isBored = true;
}, 60000);
if (xpLossTimer) {
LK.clearTimeout(xpLossTimer);
}
xpLossTimer = LK.setTimeout(function () {
if (petXp > 0) {
addXp(-XP_LOSS_AMOUNT);
statusText.showMessage("Pet is hungry! Lost " + XP_LOSS_AMOUNT + " XP!");
}
}, XP_LOSS_TIMEOUT);
// Schedule next need after play is resolved
game.scheduleNextNeed();
}
function addXp(amount) {
console.log("addXp called with amount:", amount);
petXp += amount;
storage.petXp = petXp;
// Check if pet is at level 1 and XP goes below 0
if (petLevel === 1 && petXp < 0) {
// Pet dies at level 1 with negative XP
statusText.showMessage(storage.petName + " has died!");
// Show game over
LK.setTimeout(function () {
// Reset all storage values
storage.petLevel = 1;
storage.petXp = 0;
storage.lastFed = 0;
storage.lastCleaned = 0;
storage.lastPlayed = 0;
storage.isHungry = false;
storage.isBored = false;
storage.hasActivePoop = false;
storage.petName = null; // Reset pet name to trigger character select again
// Show game over screen
LK.showGameOver();
}, 2000);
return; // Stop execution
}
// Check if pet level is greater than 1 and XP goes below 0
if (petLevel > 1 && petXp < 0) {
// Pet regresses to previous level
petLevel -= 1;
// Calculate new XP_PER_LEVEL for the previous level
XP_PER_LEVEL = BASE_XP_PER_LEVEL * Math.pow(2, petLevel - 1);
// Calculate how much XP to subtract from previous level's total
// If XP is -50 and pet was at level 2, it should have XP_PER_LEVEL - 50 (e.g., 500 - 50 = 450)
petXp = XP_PER_LEVEL + petXp; // Add negative XP to previous level's max XP
// Update storage values
storage.petLevel = petLevel;
storage.petXp = petXp;
// Update level text
levelText.setText("Level " + petLevel);
// Update pet position based on new level
if (petLevel === 1) {
petPosition = petBabyPosition;
} else if (petLevel === 2) {
petPosition = petChildPosition;
} else if (petLevel === 3) {
petPosition = petTeenPosition;
} else if (petLevel >= 4) {
petPosition = petAdultPosition;
}
// Update pet position
pet.x = petPosition.x;
pet.y = petPosition.y;
// Show message about regression
statusText.showMessage("Pet regressed to level " + petLevel + "!");
// Update pet appearance
// Remove old graphic and add new one
var petType = storage.petType || "Fluffy";
var newPetAssetId;
if (petType === "Puffy") {
if (petLevel === 1) {
newPetAssetId = 'pet_puffy_baby';
} else if (petLevel === 2) {
newPetAssetId = 'pet_puffy_child';
} else if (petLevel === 3) {
newPetAssetId = 'pet_puffy_teen';
} else if (petLevel >= 4) {
newPetAssetId = 'pet_puffy_adult';
}
} else {
// Default for Fluffy pet
if (petLevel === 1) {
newPetAssetId = 'pet_baby';
} else if (petLevel === 2) {
newPetAssetId = 'pet_child';
} else if (petLevel === 3) {
newPetAssetId = 'pet_teen';
} else if (petLevel >= 4) {
newPetAssetId = 'pet_adult';
}
}
// Get reference to current graphics to replace
var currentGraphics = pet.children.find(function (child) {
return child.asset && child.asset.indexOf('pet_') === 0;
});
if (currentGraphics) {
// Set the current pet image as invisible
currentGraphics.visible = false;
pet.removeChild(currentGraphics);
}
var petGraphics = pet.attachAsset(newPetAssetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Flash effect to indicate regression
LK.effects.flashObject(pet, 0xFF0000, 1000);
}
// Check for level up
if (petXp >= XP_PER_LEVEL) {
console.log("Level up condition met!");
petLevel += 1;
petXp = 0;
storage.petLevel = petLevel;
storage.petXp = petXp;
// Recalculate XP_PER_LEVEL for next level - doubles with each level
XP_PER_LEVEL = BASE_XP_PER_LEVEL * Math.pow(2, petLevel - 1);
console.log("New level:", petLevel, "New XP_PER_LEVEL:", XP_PER_LEVEL);
// Update pet position based on new level
if (petLevel === 2) {
petPosition = petChildPosition;
} else if (petLevel === 3) {
petPosition = petTeenPosition;
} else if (petLevel >= 4) {
petPosition = petAdultPosition;
}
// Update pet position
pet.x = petPosition.x;
pet.y = petPosition.y;
levelText.setText("Level " + petLevel);
pet.evolve();
if (petLevel >= 4) {
statusText.showMessage("Maximum level reached!");
} else {
statusText.showMessage("Pet evolved to level " + petLevel + "! Next level: " + XP_PER_LEVEL + " XP");
}
}
// Update the HP text with current XP
if (hpText) {
hpText.setText("XP: " + petXp + "/" + XP_PER_LEVEL);
} else {
console.log("hpText is undefined or null");
}
addBarFillXp();
}
function createProgressBar() {
// Initialize position first
progressBar.x = 2048 / 2 - 400; // Center it
progressBar.y = 2732 - 200; // Near bottom
barBackground = LK.getAsset('progressBar_bg', {
anchorX: 0,
anchorY: 0.5
});
progressBar.addChild(barBackground);
barFill = LK.getAsset('progressBar_fill', {
anchorX: 0,
anchorY: 0.5
});
progressBar.addChild(barFill);
// Add progress bar to game
game.addChild(progressBar);
// Update hpText position now that progressBar exists
if (hpText) {
hpText.x = progressBar.x + 10;
hpText.y = progressBar.y - 50;
}
}
function addBarFillXp() {
// Store current width to animate from
var currentWidth = barFill.width;
// Calculate target width
var targetWidth = petXp / XP_PER_LEVEL * barBackground.width;
// Animate the width change
tween(barFill, {
width: targetWidth
}, {
duration: 800,
easing: tween.easeOutQuad
});
}
// Game update function
game.update = function () {
// Screen-specific updates
if (currentScreen === "game") {
// Ensure we're not continuously updating the progress bar every frame
// This was the source of the undefined parameters call
}
};
create a cute creature baby. In-Game asset. 2d. High contrast. No shadows
create a cute logo with Pocket Creature written. In-Game asset. 2d. High contrast. No shadows
create a cute button with play written inside. In-Game asset. 2d. High contrast. No shadows
create a cute room, lo fi room. In-Game asset. 2d. High contrast. No shadows
create a cute icon for cleaning item. In-Game asset. 2d. High contrast. No shadows
reimagine as a food item
Generate a smaller younger version of this character
Generate a smaller younger and cute version of this
Zoom out so it doesnt cut any elements off screen
Reimagine as younger cute version of this character
Reimagine as a younger cute version of this character
Reimagine as a cute younger version of this character
zoom out so it doesnt cut any elements off screen, make the outline thicker
Make a drawing of a cute poop. In-Game asset. 2d. High contrast. No shadows
Create an image for a memory game's card's back. In-Game asset. 2d. High contrast. No shadows
Create an image for a memory game's card's front with an icon of a play ball. In-Game asset. 2d. High contrast. No shadows
Create an image for a memory game's card's front with an icon of a chicken leg food. In-Game asset. 2d. High contrast. No shadows
Create an image for a memory game's card's front with an icon of a cute poop. In-Game asset. 2d. High contrast. No shadows
Create an image for a memory game's card's front with an icon of a cute pocket creature. In-Game asset. 2d. High contrast. No shadows
create a cute egg with some pattern and a peach and bege colors. In-Game asset. 2d. High contrast. No shadows
create a cute egg with some pattern and a light orange and light red colors. In-Game asset. 2d. High contrast. No shadows
simplify the eyes
Create a cute speech bubble with a heart. In-Game asset. 2d. High contrast. No shadows