/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
papillaCurrency: 0,
ownedSkins: {
rojo: false,
verde: false,
morado: false,
amarillo: false
},
selectedSkin: "rojo"
});
/****
* Classes
****/
// Catcher class (player character)
var Catcher = Container.expand(function () {
var self = Container.call(this);
var catcherAsset = self.attachAsset('catcher', {
anchorX: 0.5,
anchorY: 0.5
});
// Optionally, add a face or smile with another shape or text (not required for MVP)
// No update needed; position is set by player input
return self;
});
// Optionally, play background music (commented out for MVP)
// LK.playMusic('bgmusic', {fade: {start: 0, end: 1, duration: 1000}});
// Enemy class (Tinky Winky)
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyAsset = self.attachAsset('catcher', {
anchorX: 0.5,
anchorY: 0.5
});
enemyAsset.tint = 0x8800ff; // Purple tint for Tinky Winky
self.speed = 6;
self.targetX = self.x;
self.targetY = self.y;
self.update = function () {
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > self.speed) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
} else {
self.x = self.targetX;
self.y = self.targetY;
}
};
return self;
});
// Ground class (for missed detection)
var Ground = Container.expand(function () {
var self = Container.call(this);
var groundAsset = self.attachAsset('ground', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
// Papilla class (falling object)
var Papilla = Container.expand(function () {
var self = Container.call(this);
var papillaAsset = self.attachAsset('papilla', {
anchorX: 0.5,
anchorY: 0.5
});
// Falling speed (pixels per frame)
self.speed = 8;
// Used for tracking if already counted as missed/caught
self.caught = false;
// Called every tick
self.update = function () {
self.y += self.speed;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x00b140 // Green background
});
/****
* Game Code
****/
// Game constants
/****
* Papilla Catcher - Source Code
****/
// Papilla (baby food jar) - ellipse, yellow
// Character - box, blue
// Ground - box, brown
// Sound for catching
// Sound for miss
// Music (optional, will not play by default)
var _tween = tween;
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var GROUND_HEIGHT = 40;
var MAX_MISSES = 3;
// Difficulty settings
var DIFFICULTY_SETTINGS = [{
name: "Fácil",
spawnInterval: 70,
papillaSpeed: 7
}, {
name: "Normal",
spawnInterval: 48,
papillaSpeed: 10
}, {
name: "Difícil",
spawnInterval: 28,
papillaSpeed: 16
}, {
name: "Extremo",
spawnInterval: 12,
papillaSpeed: 28
}, {
name: "Persecución",
type: "chase"
}];
var selectedDifficulty = null;
// Game state
var papillas = [];
var score = 0;
var misses = 0;
var spawnInterval = 60; // Frames between spawns (will decrease)
var papillaSpeed = 8; // Initial speed (will increase)
var lastSpawnTick = 0;
var isDragging = false;
var dragOffsetX = 0;
var isChaseMode = false;
var chaseCameraX = 0;
var chaseCameraY = 0;
var chaseMapWidth = 6144;
var chaseMapHeight = 8196;
var enemy = null;
var chaseContainer = null;
// --- Difficulty Menu UI ---
var menuContainer = new Container();
menuContainer.x = GAME_WIDTH / 2;
menuContainer.y = GAME_HEIGHT / 2;
game.addChild(menuContainer);
// Papilla currency display
var papillaCurrencyTxt = new Text2("Papillas: " + (storage.papillaCurrency || 0), {
size: 90,
fill: 0xB8B031
});
papillaCurrencyTxt.anchor.set(0.5, 1);
papillaCurrencyTxt.y = -320;
menuContainer.addChild(papillaCurrencyTxt);
// Skin shop UI
var skinNames = ["rojo", "verde", "morado", "amarillo"];
var skinColors = [0xff0000, 0x00ff00, 0x8000ff, 0xffff00];
var skinPrices = [20, 20, 20, 20];
var skinButtons = [];
var skinShopTitle = new Text2("Skins Teletubie", {
size: 100,
fill: "#444"
});
skinShopTitle.anchor.set(0.5, 1);
skinShopTitle.y = -200;
menuContainer.addChild(skinShopTitle);
for (var s = 0; s < skinNames.length; s++) {
var skinBtn = new Container();
skinBtn.x = -420 + s * 280;
skinBtn.y = -60;
// Visual skin preview (colored circle)
var skinPreview = LK.getAsset('centerCircle', {
width: 120,
height: 120,
color: skinColors[s],
anchorX: 0.5,
anchorY: 0.5
});
skinBtn.addChild(skinPreview);
// Price or owned indicator
var owned = storage.ownedSkins && storage.ownedSkins[skinNames[s]];
var priceOrOwned = new Text2(owned ? "Usar" : skinPrices[s] + " 🍼", {
size: 50,
fill: owned ? "#00b140" : "#b8b031"
});
priceOrOwned.anchor.set(0.5, 0);
priceOrOwned.y = 70;
skinBtn.addChild(priceOrOwned);
// Touch event for buying/selecting
(function (skinIdx) {
skinBtn.down = function (x, y, obj) {
var skin = skinNames[skinIdx];
var owned = storage.ownedSkins && storage.ownedSkins[skin];
if (owned) {
storage.selectedSkin = skin;
updateSkinShopUI();
} else if ((storage.papillaCurrency || 0) >= skinPrices[skinIdx]) {
storage.papillaCurrency -= skinPrices[skinIdx];
storage.ownedSkins[skin] = true;
storage.selectedSkin = skin;
papillaCurrencyTxt.setText("Papillas: " + storage.papillaCurrency);
updateSkinShopUI();
}
};
})(s);
menuContainer.addChild(skinBtn);
skinButtons.push({
btn: skinBtn,
priceOrOwned: priceOrOwned,
idx: s
});
}
// Helper to update skin shop UI
function updateSkinShopUI() {
papillaCurrencyTxt.setText("Papillas: " + (storage.papillaCurrency || 0));
for (var i = 0; i < skinButtons.length; i++) {
var skin = skinNames[i];
var owned = storage.ownedSkins && storage.ownedSkins[skin];
var selected = storage.selectedSkin === skin;
skinButtons[i].priceOrOwned.setText(owned ? selected ? "Usando" : "Usar" : skinPrices[i] + " 🍼");
skinButtons[i].btn.alpha = selected ? 1 : 0.8;
skinButtons[i].btn.scaleX = skinButtons[i].btn.scaleY = selected ? 1.15 : 1;
}
}
updateSkinShopUI();
// Difficulty menu
var menuTitle = new Text2("Elige dificultad", {
size: 160,
fill: 0x00B140
});
menuTitle.anchor.set(0.5, 1);
menuTitle.y = 120;
menuContainer.addChild(menuTitle);
var menuButtons = [];
var buttonHeight = 200;
var buttonSpacing = 60;
for (var i = 0; i < DIFFICULTY_SETTINGS.length; i++) {
var btn = new Container();
btn.y = i * (buttonHeight + buttonSpacing);
var btnBg = LK.getAsset('ground', {
width: 700,
height: buttonHeight,
color: 0x00b140,
anchorX: 0.5,
anchorY: 0.5
});
btn.addChild(btnBg);
var btnText = new Text2(DIFFICULTY_SETTINGS[i].name, {
size: 110,
fill: "#fff"
});
btnText.anchor.set(0.5, 0.5);
btnText.y = 0;
btn.addChild(btnText);
// Touch event
(function (idx) {
btn.down = function (x, y, obj) {
selectDifficulty(idx);
};
})(i);
menuContainer.addChild(btn);
menuButtons.push(btn);
}
// Reorder buttons: move Persecución (last) to first position
var reorderedButtons = [menuButtons[4], menuButtons[0], menuButtons[1], menuButtons[2], menuButtons[3]];
for (var i = 0; i < reorderedButtons.length; i++) {
reorderedButtons[i].x = 0;
reorderedButtons[i].y = i * (buttonHeight + buttonSpacing) + 320;
// Highlight "Extremo" button for fun
if (DIFFICULTY_SETTINGS[i].name === "Extremo" || i > 0 && DIFFICULTY_SETTINGS[i - 1].name === "Extremo") {
var extBtn = reorderedButtons[i];
// Animate color or scale for visibility
(function (btn) {
_tween(btn, {
scaleX: 1.12,
scaleY: 1.12
}, {
duration: 600,
easing: _tween.easeInOut
});
})(extBtn);
}
}
menuButtons = reorderedButtons;
// Chase difficulty submenu
function showChaseDifficultyMenu() {
var chaseDiffContainer = new Container();
chaseDiffContainer.x = GAME_WIDTH / 2;
chaseDiffContainer.y = GAME_HEIGHT / 2;
game.addChild(chaseDiffContainer);
// Title
var chaseTitle = new Text2("Elige dificultad", {
size: 160,
fill: 0x00B140
});
chaseTitle.anchor.set(0.5, 1);
chaseTitle.y = 120;
chaseDiffContainer.addChild(chaseTitle);
// Chase difficulty options
var chaseDiffOptions = [{
name: "Fácil",
speed: 6
}, {
name: "Normal",
speed: 10
}, {
name: "Difícil",
speed: 16
}];
var buttonHeight = 200;
var buttonSpacing = 60;
for (var i = 0; i < chaseDiffOptions.length; i++) {
var btn = new Container();
btn.y = i * (buttonHeight + buttonSpacing);
var btnBg = LK.getAsset('ground', {
width: 700,
height: buttonHeight,
color: 0x00b140,
anchorX: 0.5,
anchorY: 0.5
});
btn.addChild(btnBg);
var btnText = new Text2(chaseDiffOptions[i].name, {
size: 110,
fill: "#fff"
});
btnText.anchor.set(0.5, 0.5);
btnText.y = 0;
btn.addChild(btnText);
// Touch event
(function (diffIdx, enemySpeed) {
btn.down = function (x, y, obj) {
chaseDiffContainer.destroy();
isChaseMode = true;
enemy = null;
setupChaseMode();
if (enemy) {
enemy.speed = enemySpeed;
}
};
})(i, chaseDiffOptions[i].speed);
chaseDiffContainer.addChild(btn);
}
}
// Chase mode setup
function setupChaseMode() {
chaseContainer = new Container();
game.addChild(chaseContainer);
// Create player (catcher) in chase mode
catcher = new Catcher();
catcher.x = chaseMapWidth / 2;
catcher.y = chaseMapHeight / 2;
chaseContainer.addChild(catcher);
// Create enemy (Tinky Winky)
enemy = new Enemy();
enemy.x = chaseMapWidth / 2 - 800;
enemy.y = chaseMapHeight / 2 - 600;
enemy.speed = 8;
chaseContainer.addChild(enemy);
// Initialize camera to follow player
chaseCameraX = catcher.x;
chaseCameraY = catcher.y;
// Score display
scoreTxt = new Text2('0', {
size: 120,
fill: 0xffffff
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
updateScoreDisplay();
}
// Hide game elements until difficulty is chosen
var ground, catcher, scoreTxt, missesTxt, _tween;
function setupGameElements() {
// Create ground at bottom
ground = new Ground();
ground.x = GAME_WIDTH / 2;
ground.y = GAME_HEIGHT - GROUND_HEIGHT / 2;
game.addChild(ground);
// Create catcher (player)
catcher = new Catcher();
catcher.x = GAME_WIDTH / 2;
catcher.y = GAME_HEIGHT - GROUND_HEIGHT - catcher.height / 2 - 10;
game.addChild(catcher);
// Score display
scoreTxt = new Text2('0', {
size: 120,
fill: 0x222222
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Misses display (hearts or Xs)
missesTxt = new Text2('', {
size: 90,
fill: 0xD83318
});
missesTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(missesTxt);
missesTxt.y = 120; // Place below score
updateScoreDisplay();
}
function updateScoreDisplay() {
if (!scoreTxt) return;
scoreTxt.setText(score);
// Show misses as X X X (only in non-chase mode)
if (missesTxt) {
var missStr = '';
for (var i = 0; i < MAX_MISSES; i++) {
missStr += i < misses ? '✖ ' : '○ ';
}
missesTxt.setText(missStr);
}
}
// Called when a difficulty is selected
function selectDifficulty(idx) {
selectedDifficulty = idx;
// Remove menu
menuContainer.visible = false;
if (DIFFICULTY_SETTINGS[idx].type === "chase") {
isChaseMode = true;
showChaseDifficultyMenu();
} else {
// Set initial values based on difficulty
spawnInterval = DIFFICULTY_SETTINGS[idx].spawnInterval;
papillaSpeed = DIFFICULTY_SETTINGS[idx].papillaSpeed;
// Setup game elements
setupGameElements();
}
}
// Touch/move controls
function clamp(val, min, max) {
return Math.max(min, Math.min(max, val));
}
// Only allow horizontal movement
function handleMove(x, y, obj) {
if (selectedDifficulty === null) return;
if (isChaseMode) {
// Chase mode: don't move on hover
return;
}
if (isDragging) {
// Clamp catcher within screen
var halfWidth = catcher.width / 2;
catcher.x = clamp(x - dragOffsetX, halfWidth, GAME_WIDTH - halfWidth);
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
if (selectedDifficulty === null) return;
if (isChaseMode) {
// Convert screen coordinates to world coordinates
var worldX = chaseCameraX - GAME_WIDTH / 2 + x;
var worldY = chaseCameraY - GAME_HEIGHT / 2 + y;
// Move player to clicked location at high speed
if (catcher) {
_tween(catcher, {
x: worldX,
y: worldY
}, {
duration: 400,
easing: _tween.easeOut
});
}
return;
}
// Only start drag if touch is on or near the catcher
var local = catcher.toLocal(game.toGlobal({
x: x,
y: y
}));
if (local.x >= -catcher.width / 2 && local.x <= catcher.width / 2 && local.y >= -catcher.height / 2 && local.y <= catcher.height / 2) {
isDragging = true;
dragOffsetX = x - catcher.x;
handleMove(x, y, obj);
}
};
game.up = function (x, y, obj) {
if (selectedDifficulty === null) return;
isDragging = false;
};
// Papilla spawn logic
function spawnPapilla() {
var papilla = new Papilla();
// Random X, avoid spawning at extreme edges
var margin = 100 + papilla.width / 2;
papilla.x = margin + Math.random() * (GAME_WIDTH - 2 * margin);
papilla.y = 0 - papilla.height / 2;
papilla.speed = papillaSpeed;
papillas.push(papilla);
game.addChild(papilla);
}
// Main game update loop
game.update = function () {
// Block game logic until difficulty is selected
if (selectedDifficulty === null) {
return;
}
// Handle chase mode
if (isChaseMode) {
if (catcher && enemy) {
// Update enemy AI to chase player
enemy.targetX = catcher.x;
enemy.targetY = catcher.y;
enemy.update();
// Update camera to follow player
chaseCameraX = catcher.x;
chaseCameraY = catcher.y;
// Clamp camera to map bounds
chaseCameraX = clamp(chaseCameraX, GAME_WIDTH / 2, chaseMapWidth - GAME_WIDTH / 2);
chaseCameraY = clamp(chaseCameraY, GAME_HEIGHT / 2, chaseMapHeight - GAME_HEIGHT / 2);
// Position container to follow camera
chaseContainer.x = GAME_WIDTH / 2 - chaseCameraX;
chaseContainer.y = GAME_HEIGHT / 2 - chaseCameraY;
// Update score as time survived (in seconds)
score = Math.floor(LK.ticks / 60);
scoreTxt.setText("Tiempo: " + score + "s");
// Check if caught by enemy
if (catcher.intersects(enemy)) {
LK.effects.flashScreen(0xff0000, 400);
LK.setTimeout(function () {
LK.showGameOver();
}, 400);
}
}
return;
}
// Increase difficulty as score rises (relative to base difficulty)
if (selectedDifficulty === 3) {
// Extremo mode: always maxed out
spawnInterval = 8;
papillaSpeed = 38 + Math.floor(score / 10) * 2; // gets even faster as score increases
} else if (score < 10) {
spawnInterval = DIFFICULTY_SETTINGS[selectedDifficulty].spawnInterval;
papillaSpeed = DIFFICULTY_SETTINGS[selectedDifficulty].papillaSpeed;
} else if (score < 20) {
spawnInterval = Math.max(22, DIFFICULTY_SETTINGS[selectedDifficulty].spawnInterval - 12);
papillaSpeed = DIFFICULTY_SETTINGS[selectedDifficulty].papillaSpeed + 2;
} else if (score < 35) {
spawnInterval = Math.max(18, DIFFICULTY_SETTINGS[selectedDifficulty].spawnInterval - 22);
papillaSpeed = DIFFICULTY_SETTINGS[selectedDifficulty].papillaSpeed + 5;
} else if (score < 50) {
spawnInterval = Math.max(14, DIFFICULTY_SETTINGS[selectedDifficulty].spawnInterval - 30);
papillaSpeed = DIFFICULTY_SETTINGS[selectedDifficulty].papillaSpeed + 8;
} else {
spawnInterval = Math.max(10, DIFFICULTY_SETTINGS[selectedDifficulty].spawnInterval - 36);
papillaSpeed = DIFFICULTY_SETTINGS[selectedDifficulty].papillaSpeed + 12;
}
// Spawn new papilla
if (LK.ticks - lastSpawnTick >= spawnInterval) {
spawnPapilla();
lastSpawnTick = LK.ticks;
}
// Update papillas
for (var i = papillas.length - 1; i >= 0; i--) {
var p = papillas[i];
p.update();
// Check for catch (intersect with catcher)
if (!p.caught && p.intersects(catcher)) {
p.caught = true;
score += 1;
// Award papilla currency
storage.papillaCurrency = (storage.papillaCurrency || 0) + 1;
if (typeof papillaCurrencyTxt !== "undefined" && papillaCurrencyTxt.setText) {
papillaCurrencyTxt.setText("Papillas: " + storage.papillaCurrency);
}
if (typeof updateSkinShopUI === "function") updateSkinShopUI();
updateScoreDisplay();
LK.getSound('catch').play();
// Animate papilla (shrink and fade)
_tween(p, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 250,
easing: _tween.easeIn,
onFinish: function onFinish() {}
});
// Remove after animation
LK.setTimeout(function (papillaRef, idx) {
if (papillaRef && papillas.indexOf(papillaRef) !== -1) {
papillaRef.destroy();
papillas.splice(papillas.indexOf(papillaRef), 1);
}
}, 260, p, i);
continue;
}
// Check for miss (intersect with ground or off screen)
if (!p.caught && p.y + p.height / 2 >= ground.y - GROUND_HEIGHT / 2) {
p.caught = true;
misses += 1;
updateScoreDisplay();
LK.getSound('miss').play();
// Flash screen red
LK.effects.flashScreen(0xff0000, 400);
// Animate papilla (fall and fade)
_tween(p, {
alpha: 0,
y: ground.y + 80
}, {
duration: 300,
easing: _tween.easeIn,
onFinish: function onFinish() {}
});
LK.setTimeout(function (papillaRef, idx) {
if (papillaRef && papillas.indexOf(papillaRef) !== -1) {
papillaRef.destroy();
papillas.splice(papillas.indexOf(papillaRef), 1);
}
}, 320, p, i);
// Game over if too many misses
if (misses >= MAX_MISSES) {
LK.setTimeout(function () {
LK.showGameOver();
}, 400);
}
continue;
}
// Remove papilla if far off screen (failsafe)
if (p.y > GAME_HEIGHT + 200) {
p.destroy();
papillas.splice(i, 1);
}
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
papillaCurrency: 0,
ownedSkins: {
rojo: false,
verde: false,
morado: false,
amarillo: false
},
selectedSkin: "rojo"
});
/****
* Classes
****/
// Catcher class (player character)
var Catcher = Container.expand(function () {
var self = Container.call(this);
var catcherAsset = self.attachAsset('catcher', {
anchorX: 0.5,
anchorY: 0.5
});
// Optionally, add a face or smile with another shape or text (not required for MVP)
// No update needed; position is set by player input
return self;
});
// Optionally, play background music (commented out for MVP)
// LK.playMusic('bgmusic', {fade: {start: 0, end: 1, duration: 1000}});
// Enemy class (Tinky Winky)
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyAsset = self.attachAsset('catcher', {
anchorX: 0.5,
anchorY: 0.5
});
enemyAsset.tint = 0x8800ff; // Purple tint for Tinky Winky
self.speed = 6;
self.targetX = self.x;
self.targetY = self.y;
self.update = function () {
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > self.speed) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
} else {
self.x = self.targetX;
self.y = self.targetY;
}
};
return self;
});
// Ground class (for missed detection)
var Ground = Container.expand(function () {
var self = Container.call(this);
var groundAsset = self.attachAsset('ground', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
// Papilla class (falling object)
var Papilla = Container.expand(function () {
var self = Container.call(this);
var papillaAsset = self.attachAsset('papilla', {
anchorX: 0.5,
anchorY: 0.5
});
// Falling speed (pixels per frame)
self.speed = 8;
// Used for tracking if already counted as missed/caught
self.caught = false;
// Called every tick
self.update = function () {
self.y += self.speed;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x00b140 // Green background
});
/****
* Game Code
****/
// Game constants
/****
* Papilla Catcher - Source Code
****/
// Papilla (baby food jar) - ellipse, yellow
// Character - box, blue
// Ground - box, brown
// Sound for catching
// Sound for miss
// Music (optional, will not play by default)
var _tween = tween;
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var GROUND_HEIGHT = 40;
var MAX_MISSES = 3;
// Difficulty settings
var DIFFICULTY_SETTINGS = [{
name: "Fácil",
spawnInterval: 70,
papillaSpeed: 7
}, {
name: "Normal",
spawnInterval: 48,
papillaSpeed: 10
}, {
name: "Difícil",
spawnInterval: 28,
papillaSpeed: 16
}, {
name: "Extremo",
spawnInterval: 12,
papillaSpeed: 28
}, {
name: "Persecución",
type: "chase"
}];
var selectedDifficulty = null;
// Game state
var papillas = [];
var score = 0;
var misses = 0;
var spawnInterval = 60; // Frames between spawns (will decrease)
var papillaSpeed = 8; // Initial speed (will increase)
var lastSpawnTick = 0;
var isDragging = false;
var dragOffsetX = 0;
var isChaseMode = false;
var chaseCameraX = 0;
var chaseCameraY = 0;
var chaseMapWidth = 6144;
var chaseMapHeight = 8196;
var enemy = null;
var chaseContainer = null;
// --- Difficulty Menu UI ---
var menuContainer = new Container();
menuContainer.x = GAME_WIDTH / 2;
menuContainer.y = GAME_HEIGHT / 2;
game.addChild(menuContainer);
// Papilla currency display
var papillaCurrencyTxt = new Text2("Papillas: " + (storage.papillaCurrency || 0), {
size: 90,
fill: 0xB8B031
});
papillaCurrencyTxt.anchor.set(0.5, 1);
papillaCurrencyTxt.y = -320;
menuContainer.addChild(papillaCurrencyTxt);
// Skin shop UI
var skinNames = ["rojo", "verde", "morado", "amarillo"];
var skinColors = [0xff0000, 0x00ff00, 0x8000ff, 0xffff00];
var skinPrices = [20, 20, 20, 20];
var skinButtons = [];
var skinShopTitle = new Text2("Skins Teletubie", {
size: 100,
fill: "#444"
});
skinShopTitle.anchor.set(0.5, 1);
skinShopTitle.y = -200;
menuContainer.addChild(skinShopTitle);
for (var s = 0; s < skinNames.length; s++) {
var skinBtn = new Container();
skinBtn.x = -420 + s * 280;
skinBtn.y = -60;
// Visual skin preview (colored circle)
var skinPreview = LK.getAsset('centerCircle', {
width: 120,
height: 120,
color: skinColors[s],
anchorX: 0.5,
anchorY: 0.5
});
skinBtn.addChild(skinPreview);
// Price or owned indicator
var owned = storage.ownedSkins && storage.ownedSkins[skinNames[s]];
var priceOrOwned = new Text2(owned ? "Usar" : skinPrices[s] + " 🍼", {
size: 50,
fill: owned ? "#00b140" : "#b8b031"
});
priceOrOwned.anchor.set(0.5, 0);
priceOrOwned.y = 70;
skinBtn.addChild(priceOrOwned);
// Touch event for buying/selecting
(function (skinIdx) {
skinBtn.down = function (x, y, obj) {
var skin = skinNames[skinIdx];
var owned = storage.ownedSkins && storage.ownedSkins[skin];
if (owned) {
storage.selectedSkin = skin;
updateSkinShopUI();
} else if ((storage.papillaCurrency || 0) >= skinPrices[skinIdx]) {
storage.papillaCurrency -= skinPrices[skinIdx];
storage.ownedSkins[skin] = true;
storage.selectedSkin = skin;
papillaCurrencyTxt.setText("Papillas: " + storage.papillaCurrency);
updateSkinShopUI();
}
};
})(s);
menuContainer.addChild(skinBtn);
skinButtons.push({
btn: skinBtn,
priceOrOwned: priceOrOwned,
idx: s
});
}
// Helper to update skin shop UI
function updateSkinShopUI() {
papillaCurrencyTxt.setText("Papillas: " + (storage.papillaCurrency || 0));
for (var i = 0; i < skinButtons.length; i++) {
var skin = skinNames[i];
var owned = storage.ownedSkins && storage.ownedSkins[skin];
var selected = storage.selectedSkin === skin;
skinButtons[i].priceOrOwned.setText(owned ? selected ? "Usando" : "Usar" : skinPrices[i] + " 🍼");
skinButtons[i].btn.alpha = selected ? 1 : 0.8;
skinButtons[i].btn.scaleX = skinButtons[i].btn.scaleY = selected ? 1.15 : 1;
}
}
updateSkinShopUI();
// Difficulty menu
var menuTitle = new Text2("Elige dificultad", {
size: 160,
fill: 0x00B140
});
menuTitle.anchor.set(0.5, 1);
menuTitle.y = 120;
menuContainer.addChild(menuTitle);
var menuButtons = [];
var buttonHeight = 200;
var buttonSpacing = 60;
for (var i = 0; i < DIFFICULTY_SETTINGS.length; i++) {
var btn = new Container();
btn.y = i * (buttonHeight + buttonSpacing);
var btnBg = LK.getAsset('ground', {
width: 700,
height: buttonHeight,
color: 0x00b140,
anchorX: 0.5,
anchorY: 0.5
});
btn.addChild(btnBg);
var btnText = new Text2(DIFFICULTY_SETTINGS[i].name, {
size: 110,
fill: "#fff"
});
btnText.anchor.set(0.5, 0.5);
btnText.y = 0;
btn.addChild(btnText);
// Touch event
(function (idx) {
btn.down = function (x, y, obj) {
selectDifficulty(idx);
};
})(i);
menuContainer.addChild(btn);
menuButtons.push(btn);
}
// Reorder buttons: move Persecución (last) to first position
var reorderedButtons = [menuButtons[4], menuButtons[0], menuButtons[1], menuButtons[2], menuButtons[3]];
for (var i = 0; i < reorderedButtons.length; i++) {
reorderedButtons[i].x = 0;
reorderedButtons[i].y = i * (buttonHeight + buttonSpacing) + 320;
// Highlight "Extremo" button for fun
if (DIFFICULTY_SETTINGS[i].name === "Extremo" || i > 0 && DIFFICULTY_SETTINGS[i - 1].name === "Extremo") {
var extBtn = reorderedButtons[i];
// Animate color or scale for visibility
(function (btn) {
_tween(btn, {
scaleX: 1.12,
scaleY: 1.12
}, {
duration: 600,
easing: _tween.easeInOut
});
})(extBtn);
}
}
menuButtons = reorderedButtons;
// Chase difficulty submenu
function showChaseDifficultyMenu() {
var chaseDiffContainer = new Container();
chaseDiffContainer.x = GAME_WIDTH / 2;
chaseDiffContainer.y = GAME_HEIGHT / 2;
game.addChild(chaseDiffContainer);
// Title
var chaseTitle = new Text2("Elige dificultad", {
size: 160,
fill: 0x00B140
});
chaseTitle.anchor.set(0.5, 1);
chaseTitle.y = 120;
chaseDiffContainer.addChild(chaseTitle);
// Chase difficulty options
var chaseDiffOptions = [{
name: "Fácil",
speed: 6
}, {
name: "Normal",
speed: 10
}, {
name: "Difícil",
speed: 16
}];
var buttonHeight = 200;
var buttonSpacing = 60;
for (var i = 0; i < chaseDiffOptions.length; i++) {
var btn = new Container();
btn.y = i * (buttonHeight + buttonSpacing);
var btnBg = LK.getAsset('ground', {
width: 700,
height: buttonHeight,
color: 0x00b140,
anchorX: 0.5,
anchorY: 0.5
});
btn.addChild(btnBg);
var btnText = new Text2(chaseDiffOptions[i].name, {
size: 110,
fill: "#fff"
});
btnText.anchor.set(0.5, 0.5);
btnText.y = 0;
btn.addChild(btnText);
// Touch event
(function (diffIdx, enemySpeed) {
btn.down = function (x, y, obj) {
chaseDiffContainer.destroy();
isChaseMode = true;
enemy = null;
setupChaseMode();
if (enemy) {
enemy.speed = enemySpeed;
}
};
})(i, chaseDiffOptions[i].speed);
chaseDiffContainer.addChild(btn);
}
}
// Chase mode setup
function setupChaseMode() {
chaseContainer = new Container();
game.addChild(chaseContainer);
// Create player (catcher) in chase mode
catcher = new Catcher();
catcher.x = chaseMapWidth / 2;
catcher.y = chaseMapHeight / 2;
chaseContainer.addChild(catcher);
// Create enemy (Tinky Winky)
enemy = new Enemy();
enemy.x = chaseMapWidth / 2 - 800;
enemy.y = chaseMapHeight / 2 - 600;
enemy.speed = 8;
chaseContainer.addChild(enemy);
// Initialize camera to follow player
chaseCameraX = catcher.x;
chaseCameraY = catcher.y;
// Score display
scoreTxt = new Text2('0', {
size: 120,
fill: 0xffffff
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
updateScoreDisplay();
}
// Hide game elements until difficulty is chosen
var ground, catcher, scoreTxt, missesTxt, _tween;
function setupGameElements() {
// Create ground at bottom
ground = new Ground();
ground.x = GAME_WIDTH / 2;
ground.y = GAME_HEIGHT - GROUND_HEIGHT / 2;
game.addChild(ground);
// Create catcher (player)
catcher = new Catcher();
catcher.x = GAME_WIDTH / 2;
catcher.y = GAME_HEIGHT - GROUND_HEIGHT - catcher.height / 2 - 10;
game.addChild(catcher);
// Score display
scoreTxt = new Text2('0', {
size: 120,
fill: 0x222222
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Misses display (hearts or Xs)
missesTxt = new Text2('', {
size: 90,
fill: 0xD83318
});
missesTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(missesTxt);
missesTxt.y = 120; // Place below score
updateScoreDisplay();
}
function updateScoreDisplay() {
if (!scoreTxt) return;
scoreTxt.setText(score);
// Show misses as X X X (only in non-chase mode)
if (missesTxt) {
var missStr = '';
for (var i = 0; i < MAX_MISSES; i++) {
missStr += i < misses ? '✖ ' : '○ ';
}
missesTxt.setText(missStr);
}
}
// Called when a difficulty is selected
function selectDifficulty(idx) {
selectedDifficulty = idx;
// Remove menu
menuContainer.visible = false;
if (DIFFICULTY_SETTINGS[idx].type === "chase") {
isChaseMode = true;
showChaseDifficultyMenu();
} else {
// Set initial values based on difficulty
spawnInterval = DIFFICULTY_SETTINGS[idx].spawnInterval;
papillaSpeed = DIFFICULTY_SETTINGS[idx].papillaSpeed;
// Setup game elements
setupGameElements();
}
}
// Touch/move controls
function clamp(val, min, max) {
return Math.max(min, Math.min(max, val));
}
// Only allow horizontal movement
function handleMove(x, y, obj) {
if (selectedDifficulty === null) return;
if (isChaseMode) {
// Chase mode: don't move on hover
return;
}
if (isDragging) {
// Clamp catcher within screen
var halfWidth = catcher.width / 2;
catcher.x = clamp(x - dragOffsetX, halfWidth, GAME_WIDTH - halfWidth);
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
if (selectedDifficulty === null) return;
if (isChaseMode) {
// Convert screen coordinates to world coordinates
var worldX = chaseCameraX - GAME_WIDTH / 2 + x;
var worldY = chaseCameraY - GAME_HEIGHT / 2 + y;
// Move player to clicked location at high speed
if (catcher) {
_tween(catcher, {
x: worldX,
y: worldY
}, {
duration: 400,
easing: _tween.easeOut
});
}
return;
}
// Only start drag if touch is on or near the catcher
var local = catcher.toLocal(game.toGlobal({
x: x,
y: y
}));
if (local.x >= -catcher.width / 2 && local.x <= catcher.width / 2 && local.y >= -catcher.height / 2 && local.y <= catcher.height / 2) {
isDragging = true;
dragOffsetX = x - catcher.x;
handleMove(x, y, obj);
}
};
game.up = function (x, y, obj) {
if (selectedDifficulty === null) return;
isDragging = false;
};
// Papilla spawn logic
function spawnPapilla() {
var papilla = new Papilla();
// Random X, avoid spawning at extreme edges
var margin = 100 + papilla.width / 2;
papilla.x = margin + Math.random() * (GAME_WIDTH - 2 * margin);
papilla.y = 0 - papilla.height / 2;
papilla.speed = papillaSpeed;
papillas.push(papilla);
game.addChild(papilla);
}
// Main game update loop
game.update = function () {
// Block game logic until difficulty is selected
if (selectedDifficulty === null) {
return;
}
// Handle chase mode
if (isChaseMode) {
if (catcher && enemy) {
// Update enemy AI to chase player
enemy.targetX = catcher.x;
enemy.targetY = catcher.y;
enemy.update();
// Update camera to follow player
chaseCameraX = catcher.x;
chaseCameraY = catcher.y;
// Clamp camera to map bounds
chaseCameraX = clamp(chaseCameraX, GAME_WIDTH / 2, chaseMapWidth - GAME_WIDTH / 2);
chaseCameraY = clamp(chaseCameraY, GAME_HEIGHT / 2, chaseMapHeight - GAME_HEIGHT / 2);
// Position container to follow camera
chaseContainer.x = GAME_WIDTH / 2 - chaseCameraX;
chaseContainer.y = GAME_HEIGHT / 2 - chaseCameraY;
// Update score as time survived (in seconds)
score = Math.floor(LK.ticks / 60);
scoreTxt.setText("Tiempo: " + score + "s");
// Check if caught by enemy
if (catcher.intersects(enemy)) {
LK.effects.flashScreen(0xff0000, 400);
LK.setTimeout(function () {
LK.showGameOver();
}, 400);
}
}
return;
}
// Increase difficulty as score rises (relative to base difficulty)
if (selectedDifficulty === 3) {
// Extremo mode: always maxed out
spawnInterval = 8;
papillaSpeed = 38 + Math.floor(score / 10) * 2; // gets even faster as score increases
} else if (score < 10) {
spawnInterval = DIFFICULTY_SETTINGS[selectedDifficulty].spawnInterval;
papillaSpeed = DIFFICULTY_SETTINGS[selectedDifficulty].papillaSpeed;
} else if (score < 20) {
spawnInterval = Math.max(22, DIFFICULTY_SETTINGS[selectedDifficulty].spawnInterval - 12);
papillaSpeed = DIFFICULTY_SETTINGS[selectedDifficulty].papillaSpeed + 2;
} else if (score < 35) {
spawnInterval = Math.max(18, DIFFICULTY_SETTINGS[selectedDifficulty].spawnInterval - 22);
papillaSpeed = DIFFICULTY_SETTINGS[selectedDifficulty].papillaSpeed + 5;
} else if (score < 50) {
spawnInterval = Math.max(14, DIFFICULTY_SETTINGS[selectedDifficulty].spawnInterval - 30);
papillaSpeed = DIFFICULTY_SETTINGS[selectedDifficulty].papillaSpeed + 8;
} else {
spawnInterval = Math.max(10, DIFFICULTY_SETTINGS[selectedDifficulty].spawnInterval - 36);
papillaSpeed = DIFFICULTY_SETTINGS[selectedDifficulty].papillaSpeed + 12;
}
// Spawn new papilla
if (LK.ticks - lastSpawnTick >= spawnInterval) {
spawnPapilla();
lastSpawnTick = LK.ticks;
}
// Update papillas
for (var i = papillas.length - 1; i >= 0; i--) {
var p = papillas[i];
p.update();
// Check for catch (intersect with catcher)
if (!p.caught && p.intersects(catcher)) {
p.caught = true;
score += 1;
// Award papilla currency
storage.papillaCurrency = (storage.papillaCurrency || 0) + 1;
if (typeof papillaCurrencyTxt !== "undefined" && papillaCurrencyTxt.setText) {
papillaCurrencyTxt.setText("Papillas: " + storage.papillaCurrency);
}
if (typeof updateSkinShopUI === "function") updateSkinShopUI();
updateScoreDisplay();
LK.getSound('catch').play();
// Animate papilla (shrink and fade)
_tween(p, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 250,
easing: _tween.easeIn,
onFinish: function onFinish() {}
});
// Remove after animation
LK.setTimeout(function (papillaRef, idx) {
if (papillaRef && papillas.indexOf(papillaRef) !== -1) {
papillaRef.destroy();
papillas.splice(papillas.indexOf(papillaRef), 1);
}
}, 260, p, i);
continue;
}
// Check for miss (intersect with ground or off screen)
if (!p.caught && p.y + p.height / 2 >= ground.y - GROUND_HEIGHT / 2) {
p.caught = true;
misses += 1;
updateScoreDisplay();
LK.getSound('miss').play();
// Flash screen red
LK.effects.flashScreen(0xff0000, 400);
// Animate papilla (fall and fade)
_tween(p, {
alpha: 0,
y: ground.y + 80
}, {
duration: 300,
easing: _tween.easeIn,
onFinish: function onFinish() {}
});
LK.setTimeout(function (papillaRef, idx) {
if (papillaRef && papillas.indexOf(papillaRef) !== -1) {
papillaRef.destroy();
papillas.splice(papillas.indexOf(papillaRef), 1);
}
}, 320, p, i);
// Game over if too many misses
if (misses >= MAX_MISSES) {
LK.setTimeout(function () {
LK.showGameOver();
}, 400);
}
continue;
}
// Remove papilla if far off screen (failsafe)
if (p.y > GAME_HEIGHT + 200) {
p.destroy();
papillas.splice(i, 1);
}
}
};