/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { currentLevel: 1, attemptsCount: 0 }); /**** * Classes ****/ var Button = Container.expand(function () { var self = Container.call(this); var background = self.attachAsset('button', { anchorX: 0.5, anchorY: 0.5 }); var label = new Text2('', { size: 50, fill: 0xFFFFFF }); label.anchor.set(0.5, 0.5); self.addChild(label); self.isActive = true; // Default to active, can be overridden self.setText = function (text) { label.setText(text); return self; }; self.setColor = function (color) { background.tint = color; return self; }; self.setActive = function (active) { self.isActive = active; background.alpha = active ? 1.0 : 0.5; // Also disable interaction if not active self.interactive = active; return self; }; self.down = function (x, y, obj) { if (self.isActive) { background.alpha = 0.7; if (self.onPress) { self.onPress(); // Execute the assigned press action } LK.getSound('click').play(); } }; self.up = function (x, y, obj) { if (self.isActive) { // Restore alpha even if no specific up action defined background.alpha = 1.0; } }; // Initialize default state self.setActive(self.isActive); return self; }); var HiddenButton = Container.expand(function () { var self = Container.call(this); var background = self.attachAsset('hiddenButton', { anchorX: 0.5, anchorY: 0.5 }); background.alpha = 0; // Start invisible self.interactive = true; // Needs to be interactive self.down = function (x, y, obj) { if (self.onPress) { self.onPress(); } // Briefly flash to give feedback background.alpha = 0.3; LK.setTimeout(function () { // Only fade back if it hasn't been permanently revealed or destroyed if (background && !background._destroyed) { background.alpha = 0; } }, 200); }; return self; }); var MovingTarget = Container.expand(function () { var self = Container.call(this); var target = self.attachAsset('movingTarget', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 5; self.direction = Math.random() * Math.PI * 2; self.alpha = 0; // Start hidden self.interactive = false; // Start non-interactive self.isCaught = false; self.update = function () { // Only move if not caught and visible if (!self.isCaught && self.alpha > 0) { self.x += Math.cos(self.direction) * self.speed; self.y += Math.sin(self.direction) * self.speed; // Bounce off edges (adjust slightly for anchor) var bounds = target.getBounds(); var halfWidth = bounds.width / 2 * self.scaleX; var halfHeight = bounds.height / 2 * self.scaleY; var bounced = false; if (self.x < halfWidth || self.x > 2048 - halfWidth) { self.direction = Math.PI - self.direction; self.x = Math.max(halfWidth, Math.min(2048 - halfWidth, self.x)); // Clamp position bounced = true; } if (self.y < halfHeight || self.y > 2732 - halfHeight) { self.direction = -self.direction; self.y = Math.max(halfHeight, Math.min(2732 - halfHeight, self.y)); // Clamp position bounced = true; } // Add slight randomness on bounce to prevent boring patterns if (bounced) { self.direction += (Math.random() - 0.5) * 0.1; } } }; self.down = function (x, y, obj) { // Only trigger onPress if visible and not already caught if (self.alpha > 0 && !self.isCaught && self.onPress) { self.isCaught = true; // Mark as caught self.onPress(); } }; self.show = function () { if (!self.isCaught) { self.alpha = 1; self.interactive = true; // Make clickable when shown } }; self.hide = function () { self.alpha = 0; self.interactive = false; // Make unclickable when hidden }; return self; }); var ProgressBar = Container.expand(function () { var self = Container.call(this); var background = self.attachAsset('progressBarBackground', { anchorX: 0, anchorY: 0.5 }); var bar = self.attachAsset('progressBar', { anchorX: 0, anchorY: 0.5 }); self.progress = 0; self.setProgress = function (value) { // Ensure value is between 0 and 1 self.progress = Math.max(0, Math.min(1, value)); // Prevent scaling issues with very small values bar.scale.x = self.progress < 0.01 ? 0 : self.progress; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xFFFFFF }); /**** * Game Code ****/ var currentLevel = storage.currentLevel || 1; var maxLevel = 5; var isLevelComplete = false; // Flag to prevent multiple level completions var isTransitioning = false; // Flag to prevent actions during level transition // Declare variables that need to be accessed across functions var instructions; var mainButton; var hiddenElements = []; // Keep track of elements to clean up var clickCounter = 0; var gameTimer = 0; var timeLimit = 0; var progressBar; var movingTarget; var gameTimerId = null; // Use a specific variable for the interval/timeout ID // Set up game background var background = LK.getAsset('backgroundRect', { anchorX: 0, anchorY: 0 }); game.addChild(background); // Ensure background doesn't block interaction unless specifically needed background.interactive = false; function startGame() { console.log("Starting game, level:", currentLevel); resetGame(); // Ensure clean state before loading LK.playMusic('bgMusic'); loadLevel(currentLevel); } function resetGame() { console.log("Resetting game state"); clearLevel(); // Clear visual elements and listeners first isLevelComplete = false; isTransitioning = false; clickCounter = 0; gameTimer = 0; // Clear any existing timers more robustly if (gameTimerId) { LK.clearInterval(gameTimerId); // Use clearInterval for safety, works for setTimeout too LK.clearTimeout(gameTimerId); // Clear both just in case gameTimerId = null; } // Reset game-wide listeners explicitly if needed (though clearLevel should handle most) game.move = null; game.down = function (x, y, obj) {}; // Reset default handlers game.up = function (x, y, obj) {}; } function clearLevel() { console.log("Clearing level elements"); // Remove all level-specific elements if (instructions) { instructions.destroy(); instructions = null; } if (mainButton) { mainButton.destroy(); mainButton = null; } if (progressBar) { progressBar.destroy(); progressBar = null; } if (movingTarget) { // Ensure its update stops being called if game loop depends on it movingTarget.destroy(); movingTarget = null; } // Clear any hidden elements specifically tracked for (var i = 0; i < hiddenElements.length; i++) { if (hiddenElements[i] && !hiddenElements[i]._destroyed) { hiddenElements[i].destroy(); } } hiddenElements = []; // **Crucial:** Reset background interaction and listeners if (background) { background.interactive = false; background.down = null; // Remove specific listener background.up = null; // Remove specific listener (if any) } // **Crucial:** Reset game-level listeners game.move = null; // Remove shake listener if active // Clear any lingering timers if (gameTimerId) { LK.clearInterval(gameTimerId); LK.clearTimeout(gameTimerId); gameTimerId = null; } // Reset flags isLevelComplete = false; isTransitioning = false; } function loadLevel(level) { console.log("Loading level:", level); clearLevel(); // Ensure previous level is fully cleared BEFORE setting up new one isLevelComplete = false; // Reset completion flag for the new level isTransitioning = false; // Reset transition flag // Common setup for instructions text instructions = new Text2("", { size: 60, fill: 0x000000, align: 'center', // Center align text wordWrap: true, // Enable word wrap wordWrapWidth: 2048 - 400 // Give some padding }); instructions.anchor.set(0.5, 0); instructions.x = 2048 / 2; instructions.y = 200; game.addChild(instructions); // Set up the main button that appears in all levels mainButton = new Button(); mainButton.x = 2048 / 2; mainButton.y = 2732 / 2 + 200; // Move button down a bit mainButton.scale.set(1.5); // Uniform scaling game.addChild(mainButton); // Progress bar (used in some levels) - create but don't add yet progressBar = new ProgressBar(); progressBar.x = 2048 / 2 - progressBar.width / 2; // Center it based on its width progressBar.y = 2732 - 300; // Position near bottom progressBar.setProgress(0); // Don't add progressBar to game globally, add it in specific levels // Level-specific setup switch (level) { case 1: setupLevel1(); break; case 2: setupLevel2(); break; case 3: setupLevel3(); break; case 4: setupLevel4(); break; case 5: setupLevel5(); break; default: console.log("Game complete or invalid level"); // Game complete state currentLevel = maxLevel + 1; // Ensure we don't try to load > maxLevel instructions.setText("Congrats! All non-games complete!\nTime for some real-world fun! ð"); mainButton.setText("Play Again").setColor(0x22CC22).setActive(true); mainButton.onPress = function () { if (isTransitioning) { return; } currentLevel = 1; storage.currentLevel = currentLevel; startGame(); }; break; } } function setupLevel1() { console.log("Setting up Level 1"); instructions.setText("This is not a game.\nDon't press the button. Seriously,\nit's not reverse psychology! ð"); mainButton.setText("DO NOT PRESS").setColor(0xFF4444).setActive(true); // Make it red and active timeLimit = 5; // 5 seconds gameTimer = 0; game.addChild(progressBar); // Add progress bar for this level progressBar.setProgress(0); // Start the timer if (gameTimerId) { LK.clearInterval(gameTimerId); } // Clear previous just in case gameTimerId = LK.setInterval(function () { if (isLevelComplete || isTransitioning) { return; } // Stop timer if level done or transitioning gameTimer += 1 / 60; progressBar.setProgress(gameTimer / timeLimit); if (gameTimer >= timeLimit) { // Time's up - success condition met BEFORE button press progressBar.setProgress(1); // Ensure bar is full LK.clearInterval(gameTimerId); // Stop the timer gameTimerId = null; if (!isLevelComplete) { // Only complete if not already failed mainButton.setText("Well Done!").setColor(0x22CC22).setActive(true); instructions.setText("You waited! Good job following\nnon-instructions!"); LK.getSound('success').play(); // Allow clicking the button now to proceed mainButton.onPress = function () { if (!isLevelComplete) { completeLevel(); } }; } } }, 16); // ~60fps // Define button press action mainButton.onPress = function () { if (isLevelComplete || isTransitioning) { return; } if (gameTimer >= timeLimit) { // If time was already up, pressing is fine now (handled above) completeLevel(); } else { // Pressing the button *before* time is up is the failure condition instructions.setText("I told you not to press the button!\nTry again... Patience is key!"); gameTimer = 0; // Reset timer progressBar.setProgress(0); // Reset progress bar LK.getSound('failure').play(); // Keep the timer running } }; } function setupLevel2() { console.log("Setting up Level 2"); instructions.setText("This is not a game.\nThere is no hidden button here.\nOr is there? ðĪŦ"); mainButton.setText("NEXT LEVEL").setActive(false); // Start inactive // The trick: there IS a hidden button var hiddenBtn = new HiddenButton(); // *** CORRECTED POSITIONING *** // Place it directly centered on the main button hiddenBtn.x = mainButton.x; hiddenBtn.y = mainButton.y; // Optional: Make it slightly larger so it's easier to hit hiddenBtn.scale.set(1.5); // Match main button's scale maybe? Or keep smaller like 1.2? Let's try 1.5 console.log("Hidden button target position:", hiddenBtn.x, hiddenBtn.y); // Log position for debugging hiddenBtn.onPress = function () { if (isTransitioning || mainButton.isActive) { return; } // Don't react if already found or transitioning mainButton.setActive(true); // Activate the main button instructions.setText("You found the button that doesn't exist!\nNow you can proceed."); LK.getSound('success').play(); hiddenBtn.interactive = false; // Disable after finding // Make it flash visibly once found hiddenBtn.alpha = 0.5; LK.setTimeout(function () { // Ensure it still exists before trying to change alpha if (hiddenBtn && !hiddenBtn._destroyed) { hiddenBtn.alpha = 0; } }, 500); }; game.addChild(hiddenBtn); hiddenElements.push(hiddenBtn); // Track for cleanup mainButton.onPress = function () { if (isTransitioning) { return; } if (mainButton.isActive) { completeLevel(); } else { // This case might happen if they click the main button area // but miss the (still invisible) hidden button. instructions.setText("Find the 'non-existent' button first!\n(Hint: It might be closer than you think)"); LK.getSound('failure').play(); } }; // DEBUGGING TIP: Temporarily make the hidden button slightly visible // Add this line *after* game.addChild(hiddenBtn) to test its position: // hiddenBtn.alpha = 0.1; // REMOVE THIS FOR ACTUAL GAMEPLAY } function setupLevel3() { console.log("Setting up Level 3"); instructions.setText("This is not a game.\nClick exactly 5 times anywhere *but* the button.\nLike counting sheep, but less fluffy! ð"); mainButton.setText("CLICK COUNT: 0").setActive(false); // Start inactive clickCounter = 0; // Make the background interactive for this level background.interactive = true; background.down = function () { if (isLevelComplete || isTransitioning) { return; } clickCounter++; mainButton.setText("CLICK COUNT: " + clickCounter); LK.getSound('click').play(); // Sound for background clicks too if (clickCounter === 5) { mainButton.setText("NEXT LEVEL").setActive(true).setColor(0x22CC22); // Set active and green instructions.setText("Perfect! Exactly 5 clicks.\nNow press the button."); LK.getSound('success').play(); // **CRUCIAL:** Stop listening for background clicks now background.interactive = false; background.down = null; } else if (clickCounter > 5) { // Reset if they click too many times clickCounter = 0; mainButton.setText("CLICK COUNT: 0").setActive(false).setColor(0x4444FF); // Reset color instructions.setText("Oops! Too many clicks! Start over.\nExactly 5 clicks needed."); LK.getSound('failure').play(); // Keep background interactive to allow restarting the count } }; // Button action: only works if active (meaning count is 5) mainButton.onPress = function () { if (isTransitioning) { return; } if (mainButton.isActive) { // We already disabled background interaction when count hit 5 completeLevel(); } // No 'else' needed because inactive button won't trigger onPress }; } function setupLevel4() { console.log("Setting up Level 4"); instructions.setText("This is not a game.\nNothing to catch here. Definitely not a red herring... or is it? ð"); mainButton.setText("SKIP LEVEL?").setActive(true).setColor(0xFF8C00); // Orange color for skip? movingTarget = new MovingTarget(); movingTarget.x = 2048 / 2; movingTarget.y = 1000; // Start lower down movingTarget.scale.set(1.5); // Make it bigger game.addChild(movingTarget); var targetAppearances = 0; var maxAppearances = 5; // Limit how many times it appears var targetCaught = false; // Make the target appear periodically function showTarget() { if (targetCaught || isLevelComplete || isTransitioning || targetAppearances >= maxAppearances) { if (gameTimerId) { LK.clearInterval(gameTimerId); } // Stop trying if caught or limit reached gameTimerId = null; if (!targetCaught && targetAppearances >= maxAppearances) { // Player missed it too many times instructions.setText("Too slow! The non-target got away.\nTry the level again?"); mainButton.setText("RETRY LEVEL").setColor(0xFF4444).setActive(true); mainButton.onPress = function () { if (isTransitioning) { return; } loadLevel(currentLevel); // Reload current level }; } return; } movingTarget.show(); targetAppearances++; console.log("Target appearance:", targetAppearances); // Hide it after a short time LK.setTimeout(function () { if (movingTarget && !movingTarget._destroyed) { movingTarget.hide(); } // Schedule next appearance only if not caught if (!targetCaught) { gameTimerId = LK.setTimeout(showTarget, 2000 + Math.random() * 2000); // Wait 2-4 seconds } }, 1200); // Visible for 1.2 seconds } // Start the appearance cycle if (gameTimerId) { LK.clearTimeout(gameTimerId); } // Clear previous just in case gameTimerId = LK.setTimeout(showTarget, 1500); // Initial delay movingTarget.onPress = function () { if (targetCaught || isTransitioning) { return; } // Already caught or changing level targetCaught = true; movingTarget.isCaught = true; // Update target's internal state movingTarget.hide(); // Hide immediately // Stop the appearance timer/interval explicitly if (gameTimerId) { LK.clearInterval(gameTimerId); // Use clearInterval just in case LK.clearTimeout(gameTimerId); gameTimerId = null; } mainButton.setText("NEXT LEVEL").setColor(0x22CC22); // Green button instructions.setText("You caught the non-target!\nThis is still not a game..."); LK.getSound('success').play(); // Update main button's action to proceed mainButton.onPress = function () { if (isTransitioning) { return; } if (targetCaught) { completeLevel(); } }; }; // Initial main button action: skipping (or retrying if missed) mainButton.onPress = function () { if (isTransitioning) { return; } if (targetCaught) { // If target was caught AFTER setting the skip text (unlikely but possible) completeLevel(); } else if (targetAppearances >= maxAppearances) { // If it's in retry state loadLevel(currentLevel); // Retry } else { // If they press "Skip" before catching or missing instructions.setText("Are you sure? Catching it is more fun!\n(The non-target will reappear...)"); LK.getSound('failure').play(); // Don't actually skip, let the target cycle continue } }; } function setupLevel5() { console.log("Setting up Level 5"); instructions.setText("This is not a game.\nThere's definitely no need to shake\nyour device. But maybe just wiggle\nthe mouse/pointer rapidly? ðĪŠ"); mainButton.setText("GIVE UP").setActive(true).setColor(0xFF4444); // Red give up button var lastPositions = []; var shakeCounter = 0; var shakeThreshold = 25; // How much 'shake' needed var shakenEnough = false; game.addChild(progressBar); // Add progress bar for this level progressBar.setProgress(0); // **CRUCIAL:** Assign the move listener to game object game.move = function (x, y, obj) { if (shakenEnough || isLevelComplete || isTransitioning) { return; } // Stop listening if done var now = Date.now(); // Use time to calculate speed lastPositions.push({ x: x, y: y, time: now }); // Keep only recent positions (e.g., last 0.2 seconds) while (lastPositions.length > 0 && now - lastPositions[0].time > 200) { lastPositions.shift(); } // Calculate total distance moved in the kept timeframe if (lastPositions.length > 2) { var totalDistance = 0; var timeSpan = lastPositions[lastPositions.length - 1].time - lastPositions[0].time; for (var i = 1; i < lastPositions.length; i++) { var dx = lastPositions[i].x - lastPositions[i - 1].x; var dy = lastPositions[i].y - lastPositions[i - 1].y; totalDistance += Math.sqrt(dx * dx + dy * dy); } // Calculate 'intensity' - distance over time (pixels per millisecond) // Require a minimum distance and time span to avoid tiny jitters counting var intensity = timeSpan > 10 && totalDistance > 50 ? totalDistance / timeSpan : 0; // Detect "shaking" as high intensity movement // Adjust the threshold (e.g., 1.5 pixels/ms is quite fast) if (intensity > 1.5) { shakeCounter++; console.log("Shake detected, count:", shakeCounter, "Intensity:", intensity.toFixed(2)); progressBar.setProgress(Math.min(1, shakeCounter / shakeThreshold)); if (shakeCounter >= shakeThreshold && !shakenEnough) { shakenEnough = true; progressBar.setProgress(1); // Ensure bar is full mainButton.setText("NEXT LEVEL").setColor(0x22CC22).setActive(true); instructions.setText("You shook it! Or wiggled fast enough.\nHow did that even work?"); LK.getSound('success').play(); // **CRUCIAL:** Stop listening for movement now game.move = null; lastPositions = []; // Clear position buffer } } } }; mainButton.onPress = function () { if (isTransitioning) { return; } if (shakenEnough) { // game.move = null; // Already removed when shake condition met completeLevel(); } else { instructions.setText("You need to wiggle/shake more rapidly!"); shakeCounter = 0; // Reset counter on give up attempt? Optional. progressBar.setProgress(0); LK.getSound('failure').play(); } }; } function completeLevel() { if (isLevelComplete || isTransitioning) { console.log("Level completion already triggered or transitioning, ignoring."); return; // Prevent multiple triggers } console.log("Completing Level:", currentLevel); isLevelComplete = true; // Mark as complete isTransitioning = true; // Mark as transitioning LK.getSound('levelComplete').play(); // Stop any active level timers/intervals immediately if (gameTimerId) { LK.clearInterval(gameTimerId); LK.clearTimeout(gameTimerId); gameTimerId = null; console.log("Cleared gameTimerId on level complete."); } // Disable further interaction with level elements during transition if (mainButton) { mainButton.interactive = false; } if (movingTarget) { movingTarget.interactive = false; } if (background) { background.interactive = false; } game.move = null; // Stop shake listener if active // Display level complete message briefly var levelCompleteText = new Text2("Level " + currentLevel + " complete!\nThis wasn't a game either... or was it? ðĪ", { size: 80, fill: 0x008800, align: 'center' }); levelCompleteText.anchor.set(0.5, 0.5); levelCompleteText.x = 2048 / 2; levelCompleteText.y = 2732 / 2 - 400; // Position above center game.addChild(levelCompleteText); // Update level progress currentLevel++; storage.currentLevel = currentLevel; // Save progress // Move to next level after short delay LK.setTimeout(function () { if (levelCompleteText && !levelCompleteText._destroyed) { levelCompleteText.destroy(); } // Reset transition flag just before loading next level isTransitioning = false; loadLevel(currentLevel); // Load the next level (or end screen) }, 2500); // Increased delay slightly } // Main game loop - keep it simple game.update = function () { // Only update elements that need continuous updates, like the moving target if (movingTarget && !movingTarget._destroyed) { movingTarget.update(); } }; // Add basic input handlers at the game level (can be overridden by elements) game.down = function (x, y, obj) { // console.log("Game down:", x, y, obj ? obj.constructor.name : 'background'); // Specific elements handle their own down events }; game.up = function (x, y, obj) { // console.log("Game up:", x, y); // Specific elements handle their own up events }; // Start the game when the script loads startGame();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
currentLevel: 1,
attemptsCount: 0
});
/****
* Classes
****/
var Button = Container.expand(function () {
var self = Container.call(this);
var background = self.attachAsset('button', {
anchorX: 0.5,
anchorY: 0.5
});
var label = new Text2('', {
size: 50,
fill: 0xFFFFFF
});
label.anchor.set(0.5, 0.5);
self.addChild(label);
self.isActive = true; // Default to active, can be overridden
self.setText = function (text) {
label.setText(text);
return self;
};
self.setColor = function (color) {
background.tint = color;
return self;
};
self.setActive = function (active) {
self.isActive = active;
background.alpha = active ? 1.0 : 0.5;
// Also disable interaction if not active
self.interactive = active;
return self;
};
self.down = function (x, y, obj) {
if (self.isActive) {
background.alpha = 0.7;
if (self.onPress) {
self.onPress(); // Execute the assigned press action
}
LK.getSound('click').play();
}
};
self.up = function (x, y, obj) {
if (self.isActive) {
// Restore alpha even if no specific up action defined
background.alpha = 1.0;
}
};
// Initialize default state
self.setActive(self.isActive);
return self;
});
var HiddenButton = Container.expand(function () {
var self = Container.call(this);
var background = self.attachAsset('hiddenButton', {
anchorX: 0.5,
anchorY: 0.5
});
background.alpha = 0; // Start invisible
self.interactive = true; // Needs to be interactive
self.down = function (x, y, obj) {
if (self.onPress) {
self.onPress();
}
// Briefly flash to give feedback
background.alpha = 0.3;
LK.setTimeout(function () {
// Only fade back if it hasn't been permanently revealed or destroyed
if (background && !background._destroyed) {
background.alpha = 0;
}
}, 200);
};
return self;
});
var MovingTarget = Container.expand(function () {
var self = Container.call(this);
var target = self.attachAsset('movingTarget', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 5;
self.direction = Math.random() * Math.PI * 2;
self.alpha = 0; // Start hidden
self.interactive = false; // Start non-interactive
self.isCaught = false;
self.update = function () {
// Only move if not caught and visible
if (!self.isCaught && self.alpha > 0) {
self.x += Math.cos(self.direction) * self.speed;
self.y += Math.sin(self.direction) * self.speed;
// Bounce off edges (adjust slightly for anchor)
var bounds = target.getBounds();
var halfWidth = bounds.width / 2 * self.scaleX;
var halfHeight = bounds.height / 2 * self.scaleY;
var bounced = false;
if (self.x < halfWidth || self.x > 2048 - halfWidth) {
self.direction = Math.PI - self.direction;
self.x = Math.max(halfWidth, Math.min(2048 - halfWidth, self.x)); // Clamp position
bounced = true;
}
if (self.y < halfHeight || self.y > 2732 - halfHeight) {
self.direction = -self.direction;
self.y = Math.max(halfHeight, Math.min(2732 - halfHeight, self.y)); // Clamp position
bounced = true;
}
// Add slight randomness on bounce to prevent boring patterns
if (bounced) {
self.direction += (Math.random() - 0.5) * 0.1;
}
}
};
self.down = function (x, y, obj) {
// Only trigger onPress if visible and not already caught
if (self.alpha > 0 && !self.isCaught && self.onPress) {
self.isCaught = true; // Mark as caught
self.onPress();
}
};
self.show = function () {
if (!self.isCaught) {
self.alpha = 1;
self.interactive = true; // Make clickable when shown
}
};
self.hide = function () {
self.alpha = 0;
self.interactive = false; // Make unclickable when hidden
};
return self;
});
var ProgressBar = Container.expand(function () {
var self = Container.call(this);
var background = self.attachAsset('progressBarBackground', {
anchorX: 0,
anchorY: 0.5
});
var bar = self.attachAsset('progressBar', {
anchorX: 0,
anchorY: 0.5
});
self.progress = 0;
self.setProgress = function (value) {
// Ensure value is between 0 and 1
self.progress = Math.max(0, Math.min(1, value));
// Prevent scaling issues with very small values
bar.scale.x = self.progress < 0.01 ? 0 : self.progress;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xFFFFFF
});
/****
* Game Code
****/
var currentLevel = storage.currentLevel || 1;
var maxLevel = 5;
var isLevelComplete = false; // Flag to prevent multiple level completions
var isTransitioning = false; // Flag to prevent actions during level transition
// Declare variables that need to be accessed across functions
var instructions;
var mainButton;
var hiddenElements = []; // Keep track of elements to clean up
var clickCounter = 0;
var gameTimer = 0;
var timeLimit = 0;
var progressBar;
var movingTarget;
var gameTimerId = null; // Use a specific variable for the interval/timeout ID
// Set up game background
var background = LK.getAsset('backgroundRect', {
anchorX: 0,
anchorY: 0
});
game.addChild(background);
// Ensure background doesn't block interaction unless specifically needed
background.interactive = false;
function startGame() {
console.log("Starting game, level:", currentLevel);
resetGame(); // Ensure clean state before loading
LK.playMusic('bgMusic');
loadLevel(currentLevel);
}
function resetGame() {
console.log("Resetting game state");
clearLevel(); // Clear visual elements and listeners first
isLevelComplete = false;
isTransitioning = false;
clickCounter = 0;
gameTimer = 0;
// Clear any existing timers more robustly
if (gameTimerId) {
LK.clearInterval(gameTimerId); // Use clearInterval for safety, works for setTimeout too
LK.clearTimeout(gameTimerId); // Clear both just in case
gameTimerId = null;
}
// Reset game-wide listeners explicitly if needed (though clearLevel should handle most)
game.move = null;
game.down = function (x, y, obj) {}; // Reset default handlers
game.up = function (x, y, obj) {};
}
function clearLevel() {
console.log("Clearing level elements");
// Remove all level-specific elements
if (instructions) {
instructions.destroy();
instructions = null;
}
if (mainButton) {
mainButton.destroy();
mainButton = null;
}
if (progressBar) {
progressBar.destroy();
progressBar = null;
}
if (movingTarget) {
// Ensure its update stops being called if game loop depends on it
movingTarget.destroy();
movingTarget = null;
}
// Clear any hidden elements specifically tracked
for (var i = 0; i < hiddenElements.length; i++) {
if (hiddenElements[i] && !hiddenElements[i]._destroyed) {
hiddenElements[i].destroy();
}
}
hiddenElements = [];
// **Crucial:** Reset background interaction and listeners
if (background) {
background.interactive = false;
background.down = null; // Remove specific listener
background.up = null; // Remove specific listener (if any)
}
// **Crucial:** Reset game-level listeners
game.move = null; // Remove shake listener if active
// Clear any lingering timers
if (gameTimerId) {
LK.clearInterval(gameTimerId);
LK.clearTimeout(gameTimerId);
gameTimerId = null;
}
// Reset flags
isLevelComplete = false;
isTransitioning = false;
}
function loadLevel(level) {
console.log("Loading level:", level);
clearLevel(); // Ensure previous level is fully cleared BEFORE setting up new one
isLevelComplete = false; // Reset completion flag for the new level
isTransitioning = false; // Reset transition flag
// Common setup for instructions text
instructions = new Text2("", {
size: 60,
fill: 0x000000,
align: 'center',
// Center align text
wordWrap: true,
// Enable word wrap
wordWrapWidth: 2048 - 400 // Give some padding
});
instructions.anchor.set(0.5, 0);
instructions.x = 2048 / 2;
instructions.y = 200;
game.addChild(instructions);
// Set up the main button that appears in all levels
mainButton = new Button();
mainButton.x = 2048 / 2;
mainButton.y = 2732 / 2 + 200; // Move button down a bit
mainButton.scale.set(1.5); // Uniform scaling
game.addChild(mainButton);
// Progress bar (used in some levels) - create but don't add yet
progressBar = new ProgressBar();
progressBar.x = 2048 / 2 - progressBar.width / 2; // Center it based on its width
progressBar.y = 2732 - 300; // Position near bottom
progressBar.setProgress(0);
// Don't add progressBar to game globally, add it in specific levels
// Level-specific setup
switch (level) {
case 1:
setupLevel1();
break;
case 2:
setupLevel2();
break;
case 3:
setupLevel3();
break;
case 4:
setupLevel4();
break;
case 5:
setupLevel5();
break;
default:
console.log("Game complete or invalid level");
// Game complete state
currentLevel = maxLevel + 1; // Ensure we don't try to load > maxLevel
instructions.setText("Congrats! All non-games complete!\nTime for some real-world fun! ð");
mainButton.setText("Play Again").setColor(0x22CC22).setActive(true);
mainButton.onPress = function () {
if (isTransitioning) {
return;
}
currentLevel = 1;
storage.currentLevel = currentLevel;
startGame();
};
break;
}
}
function setupLevel1() {
console.log("Setting up Level 1");
instructions.setText("This is not a game.\nDon't press the button. Seriously,\nit's not reverse psychology! ð");
mainButton.setText("DO NOT PRESS").setColor(0xFF4444).setActive(true); // Make it red and active
timeLimit = 5; // 5 seconds
gameTimer = 0;
game.addChild(progressBar); // Add progress bar for this level
progressBar.setProgress(0);
// Start the timer
if (gameTimerId) {
LK.clearInterval(gameTimerId);
} // Clear previous just in case
gameTimerId = LK.setInterval(function () {
if (isLevelComplete || isTransitioning) {
return;
} // Stop timer if level done or transitioning
gameTimer += 1 / 60;
progressBar.setProgress(gameTimer / timeLimit);
if (gameTimer >= timeLimit) {
// Time's up - success condition met BEFORE button press
progressBar.setProgress(1); // Ensure bar is full
LK.clearInterval(gameTimerId); // Stop the timer
gameTimerId = null;
if (!isLevelComplete) {
// Only complete if not already failed
mainButton.setText("Well Done!").setColor(0x22CC22).setActive(true);
instructions.setText("You waited! Good job following\nnon-instructions!");
LK.getSound('success').play();
// Allow clicking the button now to proceed
mainButton.onPress = function () {
if (!isLevelComplete) {
completeLevel();
}
};
}
}
}, 16); // ~60fps
// Define button press action
mainButton.onPress = function () {
if (isLevelComplete || isTransitioning) {
return;
}
if (gameTimer >= timeLimit) {
// If time was already up, pressing is fine now (handled above)
completeLevel();
} else {
// Pressing the button *before* time is up is the failure condition
instructions.setText("I told you not to press the button!\nTry again... Patience is key!");
gameTimer = 0; // Reset timer
progressBar.setProgress(0); // Reset progress bar
LK.getSound('failure').play();
// Keep the timer running
}
};
}
function setupLevel2() {
console.log("Setting up Level 2");
instructions.setText("This is not a game.\nThere is no hidden button here.\nOr is there? ðĪŦ");
mainButton.setText("NEXT LEVEL").setActive(false); // Start inactive
// The trick: there IS a hidden button
var hiddenBtn = new HiddenButton();
// *** CORRECTED POSITIONING ***
// Place it directly centered on the main button
hiddenBtn.x = mainButton.x;
hiddenBtn.y = mainButton.y;
// Optional: Make it slightly larger so it's easier to hit
hiddenBtn.scale.set(1.5); // Match main button's scale maybe? Or keep smaller like 1.2? Let's try 1.5
console.log("Hidden button target position:", hiddenBtn.x, hiddenBtn.y); // Log position for debugging
hiddenBtn.onPress = function () {
if (isTransitioning || mainButton.isActive) {
return;
} // Don't react if already found or transitioning
mainButton.setActive(true); // Activate the main button
instructions.setText("You found the button that doesn't exist!\nNow you can proceed.");
LK.getSound('success').play();
hiddenBtn.interactive = false; // Disable after finding
// Make it flash visibly once found
hiddenBtn.alpha = 0.5;
LK.setTimeout(function () {
// Ensure it still exists before trying to change alpha
if (hiddenBtn && !hiddenBtn._destroyed) {
hiddenBtn.alpha = 0;
}
}, 500);
};
game.addChild(hiddenBtn);
hiddenElements.push(hiddenBtn); // Track for cleanup
mainButton.onPress = function () {
if (isTransitioning) {
return;
}
if (mainButton.isActive) {
completeLevel();
} else {
// This case might happen if they click the main button area
// but miss the (still invisible) hidden button.
instructions.setText("Find the 'non-existent' button first!\n(Hint: It might be closer than you think)");
LK.getSound('failure').play();
}
};
// DEBUGGING TIP: Temporarily make the hidden button slightly visible
// Add this line *after* game.addChild(hiddenBtn) to test its position:
// hiddenBtn.alpha = 0.1; // REMOVE THIS FOR ACTUAL GAMEPLAY
}
function setupLevel3() {
console.log("Setting up Level 3");
instructions.setText("This is not a game.\nClick exactly 5 times anywhere *but* the button.\nLike counting sheep, but less fluffy! ð");
mainButton.setText("CLICK COUNT: 0").setActive(false); // Start inactive
clickCounter = 0;
// Make the background interactive for this level
background.interactive = true;
background.down = function () {
if (isLevelComplete || isTransitioning) {
return;
}
clickCounter++;
mainButton.setText("CLICK COUNT: " + clickCounter);
LK.getSound('click').play(); // Sound for background clicks too
if (clickCounter === 5) {
mainButton.setText("NEXT LEVEL").setActive(true).setColor(0x22CC22); // Set active and green
instructions.setText("Perfect! Exactly 5 clicks.\nNow press the button.");
LK.getSound('success').play();
// **CRUCIAL:** Stop listening for background clicks now
background.interactive = false;
background.down = null;
} else if (clickCounter > 5) {
// Reset if they click too many times
clickCounter = 0;
mainButton.setText("CLICK COUNT: 0").setActive(false).setColor(0x4444FF); // Reset color
instructions.setText("Oops! Too many clicks! Start over.\nExactly 5 clicks needed.");
LK.getSound('failure').play();
// Keep background interactive to allow restarting the count
}
};
// Button action: only works if active (meaning count is 5)
mainButton.onPress = function () {
if (isTransitioning) {
return;
}
if (mainButton.isActive) {
// We already disabled background interaction when count hit 5
completeLevel();
}
// No 'else' needed because inactive button won't trigger onPress
};
}
function setupLevel4() {
console.log("Setting up Level 4");
instructions.setText("This is not a game.\nNothing to catch here. Definitely not a red herring... or is it? ð");
mainButton.setText("SKIP LEVEL?").setActive(true).setColor(0xFF8C00); // Orange color for skip?
movingTarget = new MovingTarget();
movingTarget.x = 2048 / 2;
movingTarget.y = 1000; // Start lower down
movingTarget.scale.set(1.5); // Make it bigger
game.addChild(movingTarget);
var targetAppearances = 0;
var maxAppearances = 5; // Limit how many times it appears
var targetCaught = false;
// Make the target appear periodically
function showTarget() {
if (targetCaught || isLevelComplete || isTransitioning || targetAppearances >= maxAppearances) {
if (gameTimerId) {
LK.clearInterval(gameTimerId);
} // Stop trying if caught or limit reached
gameTimerId = null;
if (!targetCaught && targetAppearances >= maxAppearances) {
// Player missed it too many times
instructions.setText("Too slow! The non-target got away.\nTry the level again?");
mainButton.setText("RETRY LEVEL").setColor(0xFF4444).setActive(true);
mainButton.onPress = function () {
if (isTransitioning) {
return;
}
loadLevel(currentLevel); // Reload current level
};
}
return;
}
movingTarget.show();
targetAppearances++;
console.log("Target appearance:", targetAppearances);
// Hide it after a short time
LK.setTimeout(function () {
if (movingTarget && !movingTarget._destroyed) {
movingTarget.hide();
}
// Schedule next appearance only if not caught
if (!targetCaught) {
gameTimerId = LK.setTimeout(showTarget, 2000 + Math.random() * 2000); // Wait 2-4 seconds
}
}, 1200); // Visible for 1.2 seconds
}
// Start the appearance cycle
if (gameTimerId) {
LK.clearTimeout(gameTimerId);
} // Clear previous just in case
gameTimerId = LK.setTimeout(showTarget, 1500); // Initial delay
movingTarget.onPress = function () {
if (targetCaught || isTransitioning) {
return;
} // Already caught or changing level
targetCaught = true;
movingTarget.isCaught = true; // Update target's internal state
movingTarget.hide(); // Hide immediately
// Stop the appearance timer/interval explicitly
if (gameTimerId) {
LK.clearInterval(gameTimerId); // Use clearInterval just in case
LK.clearTimeout(gameTimerId);
gameTimerId = null;
}
mainButton.setText("NEXT LEVEL").setColor(0x22CC22); // Green button
instructions.setText("You caught the non-target!\nThis is still not a game...");
LK.getSound('success').play();
// Update main button's action to proceed
mainButton.onPress = function () {
if (isTransitioning) {
return;
}
if (targetCaught) {
completeLevel();
}
};
};
// Initial main button action: skipping (or retrying if missed)
mainButton.onPress = function () {
if (isTransitioning) {
return;
}
if (targetCaught) {
// If target was caught AFTER setting the skip text (unlikely but possible)
completeLevel();
} else if (targetAppearances >= maxAppearances) {
// If it's in retry state
loadLevel(currentLevel); // Retry
} else {
// If they press "Skip" before catching or missing
instructions.setText("Are you sure? Catching it is more fun!\n(The non-target will reappear...)");
LK.getSound('failure').play();
// Don't actually skip, let the target cycle continue
}
};
}
function setupLevel5() {
console.log("Setting up Level 5");
instructions.setText("This is not a game.\nThere's definitely no need to shake\nyour device. But maybe just wiggle\nthe mouse/pointer rapidly? ðĪŠ");
mainButton.setText("GIVE UP").setActive(true).setColor(0xFF4444); // Red give up button
var lastPositions = [];
var shakeCounter = 0;
var shakeThreshold = 25; // How much 'shake' needed
var shakenEnough = false;
game.addChild(progressBar); // Add progress bar for this level
progressBar.setProgress(0);
// **CRUCIAL:** Assign the move listener to game object
game.move = function (x, y, obj) {
if (shakenEnough || isLevelComplete || isTransitioning) {
return;
} // Stop listening if done
var now = Date.now(); // Use time to calculate speed
lastPositions.push({
x: x,
y: y,
time: now
});
// Keep only recent positions (e.g., last 0.2 seconds)
while (lastPositions.length > 0 && now - lastPositions[0].time > 200) {
lastPositions.shift();
}
// Calculate total distance moved in the kept timeframe
if (lastPositions.length > 2) {
var totalDistance = 0;
var timeSpan = lastPositions[lastPositions.length - 1].time - lastPositions[0].time;
for (var i = 1; i < lastPositions.length; i++) {
var dx = lastPositions[i].x - lastPositions[i - 1].x;
var dy = lastPositions[i].y - lastPositions[i - 1].y;
totalDistance += Math.sqrt(dx * dx + dy * dy);
}
// Calculate 'intensity' - distance over time (pixels per millisecond)
// Require a minimum distance and time span to avoid tiny jitters counting
var intensity = timeSpan > 10 && totalDistance > 50 ? totalDistance / timeSpan : 0;
// Detect "shaking" as high intensity movement
// Adjust the threshold (e.g., 1.5 pixels/ms is quite fast)
if (intensity > 1.5) {
shakeCounter++;
console.log("Shake detected, count:", shakeCounter, "Intensity:", intensity.toFixed(2));
progressBar.setProgress(Math.min(1, shakeCounter / shakeThreshold));
if (shakeCounter >= shakeThreshold && !shakenEnough) {
shakenEnough = true;
progressBar.setProgress(1); // Ensure bar is full
mainButton.setText("NEXT LEVEL").setColor(0x22CC22).setActive(true);
instructions.setText("You shook it! Or wiggled fast enough.\nHow did that even work?");
LK.getSound('success').play();
// **CRUCIAL:** Stop listening for movement now
game.move = null;
lastPositions = []; // Clear position buffer
}
}
}
};
mainButton.onPress = function () {
if (isTransitioning) {
return;
}
if (shakenEnough) {
// game.move = null; // Already removed when shake condition met
completeLevel();
} else {
instructions.setText("You need to wiggle/shake more rapidly!");
shakeCounter = 0; // Reset counter on give up attempt? Optional.
progressBar.setProgress(0);
LK.getSound('failure').play();
}
};
}
function completeLevel() {
if (isLevelComplete || isTransitioning) {
console.log("Level completion already triggered or transitioning, ignoring.");
return; // Prevent multiple triggers
}
console.log("Completing Level:", currentLevel);
isLevelComplete = true; // Mark as complete
isTransitioning = true; // Mark as transitioning
LK.getSound('levelComplete').play();
// Stop any active level timers/intervals immediately
if (gameTimerId) {
LK.clearInterval(gameTimerId);
LK.clearTimeout(gameTimerId);
gameTimerId = null;
console.log("Cleared gameTimerId on level complete.");
}
// Disable further interaction with level elements during transition
if (mainButton) {
mainButton.interactive = false;
}
if (movingTarget) {
movingTarget.interactive = false;
}
if (background) {
background.interactive = false;
}
game.move = null; // Stop shake listener if active
// Display level complete message briefly
var levelCompleteText = new Text2("Level " + currentLevel + " complete!\nThis wasn't a game either... or was it? ðĪ", {
size: 80,
fill: 0x008800,
align: 'center'
});
levelCompleteText.anchor.set(0.5, 0.5);
levelCompleteText.x = 2048 / 2;
levelCompleteText.y = 2732 / 2 - 400; // Position above center
game.addChild(levelCompleteText);
// Update level progress
currentLevel++;
storage.currentLevel = currentLevel; // Save progress
// Move to next level after short delay
LK.setTimeout(function () {
if (levelCompleteText && !levelCompleteText._destroyed) {
levelCompleteText.destroy();
}
// Reset transition flag just before loading next level
isTransitioning = false;
loadLevel(currentLevel); // Load the next level (or end screen)
}, 2500); // Increased delay slightly
}
// Main game loop - keep it simple
game.update = function () {
// Only update elements that need continuous updates, like the moving target
if (movingTarget && !movingTarget._destroyed) {
movingTarget.update();
}
};
// Add basic input handlers at the game level (can be overridden by elements)
game.down = function (x, y, obj) {
// console.log("Game down:", x, y, obj ? obj.constructor.name : 'background');
// Specific elements handle their own down events
};
game.up = function (x, y, obj) {
// console.log("Game up:", x, y);
// Specific elements handle their own up events
};
// Start the game when the script loads
startGame();