User prompt
cambia la probabilidad a un 25 % de que te salte el jumscare y pon que al reiniciar a los 5 intentos tambien se reinicien los personajes osea que al reiniciar despues de los 5 intentos tengas que comprar todo otra vez , menos los personajes gratis , y tambien al intentar comprar un personaje y no tener esferas suficientes te coloque un texto en la pantalla que diga no tienes esferas suficientes. ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
arregla el jumscare , lo que tienes que aser es que la imagen de jumscare se coloque en toda la pantalla del juego y cuando el jumscare se coloque en toda la pantalla se ponga el sonido jumscare , y elimina eso de que se agrande y se achique la imagen ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
que no se incremente la velocidad al tocar una esfera , eso es crucial , "no se aumenta la velocidad al tocar una esfera" , y cambia el valor de las esferas a 3 puntos por esfera , y cada 5 intentos se reinicie el puntaje acumulado ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
añade lo que te pedi
User prompt
pon el ganaste o win al llegar a los 50000 puntos y los puntos acumulados que solo se muestren en el apartado de la seleccion de personajes ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
camvia el valor de las esferas a 15 puntos al tocarlas y que los pinchos al llegar el tiempo a 0 cambiar el valor de daño de los pinchos a 25
User prompt
añade un contador abajo a la izquierda de 223 segundos , cuando el contador llege a 0 segundos que las pelotitas incrementen su velocidad sumando su mitad de velocidad , afectando a las esferas y a los pinchos , tambien añade que los puntos se guarden en el apartado de elegir tu personaje , costando desagrado 50 , miedo 340 , desagrado 500 , felicidad gratis , al terminar la partida los puntos acumulados en la parte superior en el medio seran sumados al contador de esferas de el apartado de seleccion de personajes , los puntos obtenidos , abra un texto que diga los precios de cada unos de los personajes , al tocar un personaje y no tener suficientes puntos no se podra elegir ese personaje , el precio de cada uno de los personaje al tener esferas sufucientes para obtener el personaje y añadirlo a la coleccion de los personajes que obtenga las esferas restantes de mis puntos acumulados seran restados al precio de cada personaje , al no tener esferas suficientes para comprar un personaje , el personaje no podra ser seleccionado ↪💡 Consider importing and using the following plugins: @upit/storage.v1, @upit/tween.v1
User prompt
añade la imagen de jumscare y tambien su audio , al perder que haiga una provavilidad del 22.2 % de que salga en la pantalla agrandandose y achicandose , mientras que se reproduce su audio : jumscare ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
tiene que tener una jit box invisible en el texto de jugar en el cual al tocar en la jitbox de el texto JUGAR hay te lleve a elegir los personajes , y cada uno de los personajes tambien tienen que tener una jit box invisible y vien colocada para poder elegir a el personaje que querramos
User prompt
ahora quiero que al tocar la palabra jugar que ponga un texto que diga selecciona tu personaje , y abajo de el texto que esten los personajes de intensamente 1 para elegir y al tocar uno de ellos poder empesar a jugar con el , al elegir el personaje que eligamos que aiga una cuenta regresiva , 3, 2, 1, 0 , cuando el contador llege a 0 que empiese el juego
User prompt
antes de iniciar el juego tienes que poner un menu para empesar a jugar , al tocar la palabra jugar que te muestre las respectivas esferas con sus nombres cuales se encuentren estaticas en el sentro de la pantalla , y al seleccionar una de ellas se inicie con ese personaje y que tenga un contador regresivo de 3 segindos , 3 , 2 , 1 y que al llegar a 0 inicien a caer las esferas y los pinchos
User prompt
quiero que agreges un menu para poder elegir mi personaje segun las emociones que aiga para elegir , y que la velocidad de las esferas y de los pinchos aumenten poco a poco , para complicarlo un poco mas
User prompt
al tocar una esfera empesar a jugar con ese personaje , al iniciar el juego me gustaria que tenga una cuenta regresiva para iniciar a jugar , al iniciar el juego tiene que empesar a caer esferas de distintos colores , verde , azul , rojo y amarillo aleatoriamente y tambien una esfera nega con pinchos que te aga daño perdiendo vida progresivamente segun el puntaje que allas obtenido al recoger las esferas , cada esfera que obtengas sera un punto
User prompt
cambia las esferas por imagenes de los personajes , alegria por el sirculo amarillo , enojo por el sirculo rojo , desagrado por la esfera verde y miedo por la esfera purpura
User prompt
el juego tiene que estar en español
Code edit (1 edits merged)
Please save this source code
User prompt
Inside Out Memory Sphere Catcher
User prompt
la idea que tengo para crear el juego seria que haiga un personaje de intensamente el cual puedas seleccionar para cambiar de personaje y un menu de inicio , para iniciar a jugar tener opciones , EJ: vajar y suvir el volumen , y la idea es que caigan las esferas de recuerdos de intensamente 1 caigan de el cielo, y algunas corruptas o con espinas , las corruptas o con espinas que le agan daño a la personaje , el personaje tiene que ser alguno de la pelicula de intensamente 1 , alegria , enojo, desagrado o miedo, y que las esferas de recuerdos tengan un contador de puntos
Initial prompt
traduce todo lo que me digas al español
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Bubble = Container.expand(function (color) {
var self = Container.call(this);
// Color mapping - only 5 colors available, plus rainbow
var colorAssets = {
0: 'bubble_red',
1: 'bubble_blue',
2: 'bubble_green',
3: 'bubble_yellow',
4: 'bubble_purple',
5: 'bubble_red' // Start rainbow with red texture
};
self.colorType = color;
var bubbleGraphics = self.attachAsset(colorAssets[color], {
anchorX: 0.5,
anchorY: 0.5
});
// Grid position
self.gridRow = -1;
self.gridCol = -1;
self.isConnectedToTop = false;
self.visited = false;
self.isRainbow = color === 5;
// Movement properties for shooting
self.velocityX = 0;
self.velocityY = 0;
// Rainbow texture cycling
self.rainbowTimer = 0;
self.currentTextureIndex = 0;
self.rainbowTextures = ['bubble_red', 'bubble_yellow', 'bubble_green', 'bubble_blue', 'bubble_purple'];
self.update = function () {
if (self.isRainbow) {
self.rainbowTimer += 0.1;
// Change texture every 0.5 seconds (30 ticks at 60fps)
if (LK.ticks % 30 === 0) {
self.currentTextureIndex = (self.currentTextureIndex + 1) % self.rainbowTextures.length;
// Remove current texture and add new one
self.removeChild(bubbleGraphics);
bubbleGraphics = self.attachAsset(self.rainbowTextures[self.currentTextureIndex], {
anchorX: 0.5,
anchorY: 0.5
});
}
}
};
return self;
});
var BubbleShooter = Container.expand(function () {
var self = Container.call(this);
// Game grid - hexagonal arrangement
self.bubbleGrid = [];
self.gridRows = 12;
self.gridCols = 16; // Increase columns to cover full 2048px width
self.bubbleRadius = 64; // Adjust radius for proper spacing across 2048px
self.startY = 20; // Start bubbles higher up
// Shooting mechanics
self.cannon = null;
self.currentBubble = null;
self.nextBubble = null;
self.aimLine = [];
self.shootingBubble = null;
self.canShoot = true; // Flag to control when shooting is allowed
// Pressure system
self.missedShots = 0;
self.maxMissedShots = 5;
// Pressure timer system for automatic grid descent
self.pressureTimer = 0;
self.pressureInterval = 300; // Every 5 seconds at 60 FPS (adjustable)
// Initialize grid
self.initGrid = function () {
self.bubbleGrid = []; // Reset grid completely
for (var row = 0; row < self.gridRows; row++) {
self.bubbleGrid[row] = [];
for (var col = 0; col < self.gridCols; col++) {
self.bubbleGrid[row][col] = null;
}
}
// Create danger line
self.dangerLine = self.attachAsset('danger_line', {
anchorX: 0.5,
anchorY: 0.5
});
self.dangerLine.x = 1024;
self.dangerLine.y = 1800; // Position lower than middle
// Create invisible bottom boundary line for bubble expansion
self.bottomBoundary = LK.getAsset('danger_line', {
anchorX: 0.5,
anchorY: 0.5
});
self.bottomBoundary.x = 1024;
self.bottomBoundary.y = 2600; // Near bottom of screen
self.bottomBoundary.alpha = 0; // Make it invisible
self.addChild(self.bottomBoundary);
};
// Get hexagonal position - support infinite rows
self.getHexPosition = function (row, col) {
var radius = self.bubbleRadius;
var diameter = radius * 2;
var rowHeight = radius * 1.732; // Factor de compresión vertical (sqrt(3))
// 1. CÁLCULO DE CENTRADO (Para colocar la grilla completa en el centro de 2048px)
// Se calcula el ancho total teórico de la grilla más el radio final.
var totalGridWidth = self.gridCols * diameter + radius;
var centerOffset = (2048 - totalGridWidth) / 2;
// 2. DESPLAZAMIENTO HEXAGONAL (Mueve las filas impares medio diámetro)
var offsetX = row % 2 * radius;
// POSICIÓN X: (Offset de Centrado) + (Posición por columna) + (Offset hexagonal)
var x = centerOffset + col * diameter + offsetX;
// POSICIÓN Y: (Inicio del Nivel) + (Posición por fila)
var y = self.startY + row * rowHeight;
return {
x: x,
y: y
};
};
// Create initial bubble layout
self.createInitialBubbles = function () {
// Only create bubbles in first 6 rows to leave space for gameplay
var initialRows = Math.min(6, self.gridRows);
for (var row = 0; row < initialRows; row++) {
// The hexagonal grid has N and N-1 columns pattern (16, 15, 16, 15...)
var colsInRow = self.gridCols - row % 2; // 16 or 15 columns
for (var col = 0; col < colsInRow; col++) {
if (Math.random() < 0.8) {
// 80% probability for balanced coverage
// Only normal colors for ceiling bubbles - no rainbow bubbles
var color = Math.floor(Math.random() * 5); // 5 normal colors available
var bubble = new Bubble(color);
var pos = self.getHexPosition(row, col);
bubble.x = pos.x;
bubble.y = pos.y;
bubble.gridRow = row;
bubble.gridCol = col;
self.bubbleGrid[row][col] = bubble;
self.addChild(bubble);
}
}
}
};
// Get neighbors in hexagonal grid
self.getNeighbors = function (row, col) {
var neighbors = [];
var isOddRow = row % 2 === 1;
// Define neighbor offsets for hex grid
var offsets = isOddRow ? [[-1, 0], [-1, 1], [0, -1], [0, 1], [1, 0], [1, 1]] : [[-1, -1], [-1, 0], [0, -1], [0, 1], [1, -1], [1, 0]];
for (var i = 0; i < offsets.length; i++) {
var newRow = row + offsets[i][0];
var newCol = col + offsets[i][1];
if (newRow >= 0 && newRow < self.bubbleGrid.length && newCol >= 0 && newCol < self.gridCols) {
// Ensure the row exists before checking
if (!self.bubbleGrid[newRow]) {
self.bubbleGrid[newRow] = new Array(self.gridCols).fill(null);
}
neighbors.push({
row: newRow,
col: newCol
});
}
}
return neighbors;
};
// Find connected bubbles of same color
self.findConnectedBubbles = function (startRow, startCol, color) {
var connected = [];
var visited = [];
// Initialize visited array to match current grid size
for (var r = 0; r < self.bubbleGrid.length; r++) {
visited[r] = [];
for (var c = 0; c < self.gridCols; c++) {
visited[r][c] = false;
}
}
// Depth-first search
function dfs(row, col) {
if (row < 0 || row >= self.bubbleGrid.length || col < 0 || col >= self.gridCols || !visited[row] || visited[row][col]) {
return;
}
// Check if row exists and has bubbles
if (!self.bubbleGrid[row] || !self.bubbleGrid[row][col] || self.bubbleGrid[row][col].colorType !== color) {
return;
}
visited[row][col] = true;
connected.push({
row: row,
col: col
});
var neighbors = self.getNeighbors(row, col);
for (var i = 0; i < neighbors.length; i++) {
dfs(neighbors[i].row, neighbors[i].col);
}
}
dfs(startRow, startCol);
return connected;
};
// Check which bubbles are connected to top
self.markConnectedToTop = function () {
// Reset all connection flags
for (var row = 0; row < self.bubbleGrid.length; row++) {
if (self.bubbleGrid[row]) {
for (var col = 0; col < self.gridCols; col++) {
if (self.bubbleGrid[row][col]) {
self.bubbleGrid[row][col].isConnectedToTop = false;
self.bubbleGrid[row][col].visited = false;
}
}
}
}
// Mark bubbles connected to top row
function markConnected(row, col) {
if (row < 0 || row >= self.bubbleGrid.length || col < 0 || col >= self.gridCols) {
return;
}
// Ensure row exists
if (!self.bubbleGrid[row]) {
self.bubbleGrid[row] = new Array(self.gridCols).fill(null);
}
if (!self.bubbleGrid[row][col] || self.bubbleGrid[row][col].visited) {
return;
}
self.bubbleGrid[row][col].isConnectedToTop = true;
self.bubbleGrid[row][col].visited = true;
var neighbors = self.getNeighbors(row, col);
for (var i = 0; i < neighbors.length; i++) {
markConnected(neighbors[i].row, neighbors[i].col);
}
}
// Start from top row
for (var col = 0; col < self.gridCols; col++) {
if (self.bubbleGrid[0][col]) {
markConnected(0, col);
}
}
};
// Remove floating bubbles
self.removeFloatingBubbles = function () {
var removed = [];
for (var row = 0; row < self.bubbleGrid.length; row++) {
if (!self.bubbleGrid[row]) continue;
for (var col = 0; col < self.gridCols; col++) {
var bubble = self.bubbleGrid[row][col];
if (bubble && !bubble.isConnectedToTop) {
removed.push(bubble);
self.bubbleGrid[row][col] = null;
// Animate falling
tween(bubble, {
y: 2800,
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish(bubbleRef) {
return function () {
if (bubbleRef && bubbleRef.destroy) {
bubbleRef.destroy();
}
};
}(bubble)
});
}
}
}
return removed.length;
};
// Process bubble elimination
self.processBubbleElimination = function (row, col) {
var bubble = self.bubbleGrid[row][col];
if (!bubble) return;
// Special rainbow bubble behavior
if (bubble.isRainbow) {
// Rainbow bubble makes ALL touching bubbles fall (regardless of color)
var neighbors = self.getNeighbors(row, col);
var bubblesAffected = [];
// Add the rainbow bubble itself
bubblesAffected.push({
row: row,
col: col
});
// Add all neighboring bubbles
for (var n = 0; n < neighbors.length; n++) {
var neighborRow = neighbors[n].row;
var neighborCol = neighbors[n].col;
if (self.bubbleGrid[neighborRow] && self.bubbleGrid[neighborRow][neighborCol]) {
bubblesAffected.push({
row: neighborRow,
col: neighborCol
});
}
}
// Remove all affected bubbles
var redBubblesRemoved = 0;
for (var i = 0; i < bubblesAffected.length; i++) {
var pos = bubblesAffected[i];
var bubbleToRemove = self.bubbleGrid[pos.row][pos.col];
if (bubbleToRemove) {
// Count red bubbles (colorType 0 is red)
if (bubbleToRemove.colorType === 0) {
redBubblesRemoved++;
}
self.bubbleGrid[pos.row][pos.col] = null;
LK.effects.flashObject(bubbleToRemove, 0xffffff, 300);
bubbleToRemove.destroy();
}
}
// Update score for rainbow elimination
LK.setScore(LK.getScore() + bubblesAffected.length * 20);
LK.getSound('collect').play();
// Check for floating bubbles
self.markConnectedToTop();
var floatingCount = self.removeFloatingBubbles();
if (floatingCount > 0) {
LK.setScore(LK.getScore() + floatingCount * 5);
}
self.missedShots = 0;
return true;
}
var connected = self.findConnectedBubbles(row, col, bubble.colorType);
if (connected.length >= 3) {
// Remove connected bubbles
for (var i = 0; i < connected.length; i++) {
var pos = connected[i];
var bubbleToRemove = self.bubbleGrid[pos.row][pos.col];
if (bubbleToRemove) {
self.bubbleGrid[pos.row][pos.col] = null;
LK.effects.flashObject(bubbleToRemove, 0xffffff, 300);
bubbleToRemove.destroy();
}
}
// Update score
LK.setScore(LK.getScore() + connected.length * 10);
LK.getSound('collect').play();
// Check for floating bubbles
self.markConnectedToTop();
var floatingCount = self.removeFloatingBubbles();
if (floatingCount > 0) {
LK.setScore(LK.getScore() + floatingCount * 5);
}
self.missedShots = 0; // Reset pressure counter on successful match
return true;
}
return false;
};
// Move grid down (pressure system) - allow infinite expansion
self.moveGridDown = function () {
// Expand grid if needed
var maxUsedRow = -1;
for (var row = 0; row < self.bubbleGrid.length; row++) {
for (var col = 0; col < self.gridCols; col++) {
if (self.bubbleGrid[row] && self.bubbleGrid[row][col]) {
maxUsedRow = Math.max(maxUsedRow, row);
}
}
}
// Add more rows if bubbles are near the bottom
if (maxUsedRow >= self.bubbleGrid.length - 3) {
for (var i = 0; i < 5; i++) {
// Add 5 more rows
self.bubbleGrid.push(new Array(self.gridCols).fill(null));
}
}
// Move all bubbles down one row
for (var row = self.bubbleGrid.length - 1; row >= 0; row--) {
for (var col = 0; col < self.gridCols; col++) {
if (self.bubbleGrid[row] && self.bubbleGrid[row][col] && row < self.bubbleGrid.length - 1) {
// Ensure next row exists
if (!self.bubbleGrid[row + 1]) {
self.bubbleGrid[row + 1] = new Array(self.gridCols).fill(null);
}
// Move bubble to next row
var bubble = self.bubbleGrid[row][col];
self.bubbleGrid[row + 1][col] = bubble;
self.bubbleGrid[row][col] = null;
bubble.gridRow = row + 1;
var newPos = self.getHexPosition(row + 1, col);
tween(bubble, {
x: newPos.x,
y: newPos.y
}, {
duration: 300
});
}
}
}
};
// Create new shooting bubble
self.createNewBubble = function () {
if (!self.currentBubble) {
var color;
if (Math.random() < 0.1) {
// 10% chance for rainbow bubble
color = 5;
} else {
// 90% chance for normal colors
color = Math.floor(Math.random() * 5);
}
self.currentBubble = new Bubble(color);
// Position bubble properly within BubbleShooter container
self.currentBubble.x = self.cannon ? self.cannon.x : 1024;
self.currentBubble.y = self.cannon ? self.cannon.y - 60 : 2500;
self.addChild(self.currentBubble);
}
if (!self.nextBubble) {
var nextColor;
if (Math.random() < 0.1) {
// 10% chance for rainbow bubble
nextColor = 5;
} else {
// 90% chance for normal colors
nextColor = Math.floor(Math.random() * 5);
}
self.nextBubble = new Bubble(nextColor);
// Position next bubble properly within BubbleShooter container
self.nextBubble.x = self.cannon ? self.cannon.x + 100 : 1124;
self.nextBubble.y = self.cannon ? self.cannon.y - 60 : 2500;
self.nextBubble.alpha = 0.7;
self.addChild(self.nextBubble);
}
};
// Check win condition - always return false to prevent winning
self.checkWin = function () {
return false;
};
// Add new row from top
self.addNewRowFromTop = function () {
// Shift all existing bubbles down one row
for (var row = self.gridRows - 1; row > 0; row--) {
for (var col = 0; col < self.gridCols; col++) {
if (self.bubbleGrid[row - 1][col]) {
var bubble = self.bubbleGrid[row - 1][col];
self.bubbleGrid[row][col] = bubble;
self.bubbleGrid[row - 1][col] = null;
bubble.gridRow = row;
var newPos = self.getHexPosition(row, col);
tween(bubble, {
x: newPos.x,
y: newPos.y
}, {
duration: 300
});
}
}
}
// Add new bubbles to top row
for (var col = 0; col < self.gridCols; col++) {
if (Math.random() < 0.85) {
// 85% chance to place bubble for fuller coverage
// Only normal colors for ceiling bubbles - no rainbow bubbles
var color = Math.floor(Math.random() * 5); // 5 colors available
var bubble = new Bubble(color);
var pos = self.getHexPosition(0, col);
bubble.x = pos.x;
bubble.y = pos.y - 100; // Start above screen
bubble.gridRow = 0;
bubble.gridCol = col;
self.bubbleGrid[0][col] = bubble;
self.addChild(bubble);
// Animate drop into position
tween(bubble, {
y: pos.y
}, {
duration: 500,
easing: tween.bounceOut
});
}
}
};
// Shoot bubble with physics
self.shootBubble = function (angle) {
if (self.currentBubble && self.canShoot) {
var bullet = self.currentBubble;
// Use shootPower from global scope (defined as 15)
var power = 15;
// Calculate velocity components
bullet.velocityX = Math.cos(angle) * power;
bullet.velocityY = Math.sin(angle) * power;
// Set bullet to cannon position - ensure proper positioning within container
bullet.x = self.cannon ? self.cannon.x : 1024;
bullet.y = self.cannon ? self.cannon.y : 2600;
// Make it the shooting bubble
self.shootingBubble = bullet;
// Disable shooting until collision
self.canShoot = false;
// Prepare next bubble
self.currentBubble = self.nextBubble;
if (self.currentBubble) {
self.currentBubble.x = self.cannon ? self.cannon.x : 1024;
self.currentBubble.y = self.cannon ? self.cannon.y - 60 : 2500;
self.currentBubble.alpha = 1;
}
var nextColor;
if (Math.random() < 0.1) {
// 10% chance for rainbow bubble
nextColor = 5;
} else {
// 90% chance for normal colors
nextColor = Math.floor(Math.random() * 5);
}
self.nextBubble = new Bubble(nextColor);
self.nextBubble.x = self.cannon ? self.cannon.x + 100 : 1124;
self.nextBubble.y = self.cannon ? self.cannon.y - 60 : 2500;
self.nextBubble.alpha = 0.7;
self.addChild(self.nextBubble);
}
};
// Check collision between shooting bubble and grid - optimized collision detection
self.checkCollision = function (shootingBubble) {
// Only check rows that the shooting bubble could actually hit
var minRow = Math.max(0, Math.floor((shootingBubble.y - self.bubbleRadius * 2) / (self.bubbleRadius * 1.7)));
var maxRow = Math.min(self.bubbleGrid.length - 1, Math.floor((shootingBubble.y + self.bubbleRadius * 2) / (self.bubbleRadius * 1.7)));
for (var row = minRow; row <= maxRow; row++) {
if (self.bubbleGrid[row]) {
for (var col = 0; col < self.gridCols; col++) {
var gridBubble = self.bubbleGrid[row][col];
if (gridBubble) {
var dx = shootingBubble.x - gridBubble.x;
var dy = shootingBubble.y - gridBubble.y;
var distanceSquared = dx * dx + dy * dy;
// Use squared distance to avoid expensive sqrt calculation
// 85^2 = 7225 for better collision detection
if (distanceSquared <= 7225) {
return {
row: row,
col: col,
bubble: gridBubble
};
}
}
}
}
}
return null;
};
// Find closest grid position for bubble attachment - optimized and simplified
self.findClosestGridPosition = function (x, y, collisionBubble) {
var minDist = 999999;
var targetRow = -1;
var targetCol = -1;
// If we have a collision bubble, only check its immediate neighbors
if (collisionBubble) {
var neighbors = self.getNeighbors(collisionBubble.row, collisionBubble.col);
for (var n = 0; n < neighbors.length; n++) {
var nRow = neighbors[n].row;
var nCol = neighbors[n].col;
// Ensure valid bounds
if (nRow >= 0 && nCol >= 0 && nCol < self.gridCols) {
// Ensure row exists
while (self.bubbleGrid.length <= nRow) {
self.bubbleGrid.push(new Array(self.gridCols).fill(null));
}
if (!self.bubbleGrid[nRow]) {
self.bubbleGrid[nRow] = new Array(self.gridCols).fill(null);
}
if (!self.bubbleGrid[nRow][nCol]) {
var pos = self.getHexPosition(nRow, nCol);
var dx = x - pos.x;
var dy = y - pos.y;
var distSquared = dx * dx + dy * dy;
if (distSquared < minDist) {
minDist = distSquared;
targetRow = nRow;
targetCol = nCol;
}
}
}
}
if (targetRow >= 0 && targetCol >= 0) {
return {
row: targetRow,
col: targetCol
};
}
}
// Fallback: find position in first few rows
var searchRow = Math.max(0, Math.min(5, Math.floor((y - self.startY) / (self.bubbleRadius * 1.7))));
// Ensure the row exists
while (self.bubbleGrid.length <= searchRow + 2) {
self.bubbleGrid.push(new Array(self.gridCols).fill(null));
}
// Search a small area around the calculated position
for (var rowOffset = 0; rowOffset < 3; rowOffset++) {
var row = searchRow + rowOffset;
if (row >= 0 && row < self.bubbleGrid.length && self.bubbleGrid[row]) {
for (var col = 0; col < self.gridCols; col++) {
if (!self.bubbleGrid[row][col]) {
return {
row: row,
col: col
};
}
}
}
}
// If no position found, return first available position in first row
for (var col = 0; col < self.gridCols; col++) {
if (!self.bubbleGrid[0][col]) {
return {
row: 0,
col: col
};
}
}
// Last resort: return valid position
return {
row: 0,
col: 0
};
};
// Check lose condition - bubbles touching red danger line
self.checkLose = function () {
// Check if any bubble touches the danger line
for (var row = 0; row < self.bubbleGrid.length; row++) {
if (self.bubbleGrid[row]) {
for (var col = 0; col < self.gridCols; col++) {
var bubble = self.bubbleGrid[row][col];
if (bubble) {
// Check if bubble position is at or below danger line
if (bubble.y + self.bubbleRadius >= self.dangerLine.y - 10) {
return true;
}
}
}
}
}
return false;
};
// Update method to handle shooting physics - optimized
self.update = function () {
// Pressure system - increment timer and check for automatic descent
self.pressureTimer++;
if (self.pressureTimer >= self.pressureInterval) {
self.moveGridDown(); // Move grid down automatically
self.pressureTimer = 0; // Reset timer
// Check lose condition immediately after moving grid down
if (self.checkLose()) {
LK.showGameOver();
return;
}
}
// Check if bubbles are approaching bottom boundary and generate new rows
var maxBubbleY = -1;
for (var row = 0; row < self.bubbleGrid.length; row++) {
if (self.bubbleGrid[row]) {
for (var col = 0; col < self.gridCols; col++) {
if (self.bubbleGrid[row][col]) {
maxBubbleY = Math.max(maxBubbleY, self.bubbleGrid[row][col].y);
}
}
}
}
// If bubbles are getting close to bottom boundary, add new rows from top
if (maxBubbleY > self.bottomBoundary.y - 200) {
self.addNewRowFromTop();
}
// Update shooting bubble physics
if (self.shootingBubble) {
var bullet = self.shootingBubble;
// Move bullet with velocity
bullet.x += bullet.velocityX;
bullet.y += bullet.velocityY;
// Wall bouncing - reverse X velocity when hitting side walls
if (bullet.x <= self.bubbleRadius) {
bullet.x = self.bubbleRadius;
bullet.velocityX = Math.abs(bullet.velocityX); // Bounce right
} else if (bullet.x >= 2048 - self.bubbleRadius) {
bullet.x = 2048 - self.bubbleRadius;
bullet.velocityX = -Math.abs(bullet.velocityX); // Bounce left
}
// Check collision with existing bubbles
var collision = self.checkCollision(bullet);
var hitTop = bullet.y <= self.startY + self.bubbleRadius;
var shouldAttach = collision || hitTop;
if (shouldAttach) {
// Find closest empty grid position
var closestPos = self.findClosestGridPosition(bullet.x, bullet.y, collision);
if (closestPos && closestPos.row >= 0 && closestPos.col >= 0 && closestPos.col < self.gridCols) {
// Ensure row exists
while (self.bubbleGrid.length <= closestPos.row) {
self.bubbleGrid.push(new Array(self.gridCols).fill(null));
}
if (!self.bubbleGrid[closestPos.row]) {
self.bubbleGrid[closestPos.row] = new Array(self.gridCols).fill(null);
}
// Attach bubble to grid
var gridPos = self.getHexPosition(closestPos.row, closestPos.col);
bullet.x = gridPos.x;
bullet.y = gridPos.y;
bullet.gridRow = closestPos.row;
bullet.gridCol = closestPos.col;
// Add to grid
self.bubbleGrid[closestPos.row][closestPos.col] = bullet;
// Process bubble elimination
var eliminated = self.processBubbleElimination(closestPos.row, closestPos.col);
if (!eliminated) {
// No match found - increment missed shots
self.missedShots++;
if (self.missedShots >= self.maxMissedShots) {
self.moveGridDown();
self.missedShots = 0;
}
}
} else {
// No valid position found - remove bullet
bullet.destroy();
self.missedShots++;
if (self.missedShots >= self.maxMissedShots) {
self.moveGridDown();
self.missedShots = 0;
}
}
// Clear shooting bubble and re-enable shooting
self.shootingBubble = null;
self.canShoot = true;
// Create new bubble for next shot
self.createNewBubble();
// Update missed shots display
if (missedShotsText) {
missedShotsText.setText('Fallos: ' + self.missedShots + '/' + self.maxMissedShots);
}
// Check lose condition
if (self.checkLose()) {
LK.showGameOver();
return;
}
}
}
};
return self;
});
// Add spikes around the corrupted memory
var CorruptedMemory = Container.expand(function () {
var self = Container.call(this);
var memoryGraphics = self.attachAsset('corruptedMemory', {
anchorX: 0.5,
anchorY: 0.5
});
// Add spikes around the corrupted memory
var spike1 = self.attachAsset('spike', {
anchorX: 0.5,
anchorY: 0.5
});
spike1.x = -30;
spike1.y = -30;
spike1.rotation = Math.PI / 4;
var spike2 = self.attachAsset('spike', {
anchorX: 0.5,
anchorY: 0.5
});
spike2.x = 30;
spike2.y = -30;
spike2.rotation = -Math.PI / 4;
var spike3 = self.attachAsset('spike', {
anchorX: 0.5,
anchorY: 0.5
});
spike3.x = 30;
spike3.y = 30;
spike3.rotation = Math.PI / 4;
var spike4 = self.attachAsset('spike', {
anchorX: 0.5,
anchorY: 0.5
});
spike4.x = -30;
spike4.y = 30;
spike4.rotation = -Math.PI / 4;
self.speed = 4;
self.lastY = undefined;
self.update = function () {
self.y += self.speed;
};
return self;
});
var EmotionCharacter = Container.expand(function (emotionType) {
var self = Container.call(this);
var characterGraphics = self.attachAsset(emotionType, {
anchorX: 0.5,
anchorY: 0.5
});
self.emotionType = emotionType;
self.health = 100;
self.maxHealth = 100;
return self;
});
var GoldenMemory = Container.expand(function () {
var self = Container.call(this);
var memoryGraphics = self.attachAsset('goldenMemory', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 3;
self.lastY = undefined;
self.update = function () {
self.y += self.speed;
};
return self;
});
var Pipe = Container.expand(function (isTop, gapY) {
var self = Container.call(this);
var pipeGraphics = self.attachAsset('spike', {
anchorX: 0.5,
anchorY: isTop ? 1 : 0
});
pipeGraphics.tint = 0x4CAF50;
pipeGraphics.width = pipeWidth;
pipeGraphics.height = isTop ? gapY - pipeGap / 2 : 2732 - (gapY + pipeGap / 2);
self.speed = 3;
self.isTop = isTop;
self.scored = false;
self.lastX = undefined;
self.update = function () {
self.x -= self.speed;
};
return self;
});
var SideSpike = Container.expand(function (fromLeft) {
var self = Container.call(this);
var spikeGraphics = self.attachAsset('espinas', {
anchorX: 0.5,
anchorY: 0.5
});
self.fromLeft = fromLeft;
self.speed = fromLeft ? 8 : -8;
self.lastX = undefined;
self.update = function () {
self.x += self.speed;
};
return self;
});
var SnakeGame = Container.expand(function () {
var self = Container.call(this);
// Grid configuration
self.gridSize = 40; // Pixels per cell
self.gridWidth = 50; // Cells horizontally
self.gridHeight = 35; // Cells vertically
// Snake state
self.snake = [{
x: 10,
y: 10
}]; // Start position
self.direction = 'right';
self.nextDirection = 'right';
self.moveTimer = 0;
self.moveInterval = 15; // Move every 15 frames
// Food
self.foodPosition = null;
// Visual elements for destroy/recreate pattern
self.gusanoSprites = [];
self.foodSprite = null;
// Convert grid coordinates to pixel coordinates
self.getPixelPosition = function (gridX, gridY) {
var floorLeft = self.floor.x - 1800 / 2;
var floorTop = self.floor.y - 2400 / 2;
return {
x: floorLeft + gridX * self.gridSize + self.gridSize / 2,
y: floorTop + gridY * self.gridSize + self.gridSize / 2
};
};
// Place food at random empty position
self.placeFood = function () {
var validPositions = [];
// Find all empty positions
for (var x = 0; x < self.gridWidth; x++) {
for (var y = 0; y < self.gridHeight; y++) {
var isEmpty = true;
// Check if position is occupied by snake
for (var i = 0; i < self.snake.length; i++) {
if (self.snake[i].x === x && self.snake[i].y === y) {
isEmpty = false;
break;
}
}
if (isEmpty) {
validPositions.push({
x: x,
y: y
});
}
}
}
if (validPositions.length > 0) {
var randomIndex = Math.floor(Math.random() * validPositions.length);
return validPositions[randomIndex];
}
// Fallback if no valid positions
return {
x: 0,
y: 0
};
};
// Initialize game
self.initGame = function () {
// Increase gridSize dramatically for better visibility
self.gridSize = 120;
// Make snake movement slower
self.moveInterval = 15; // Move every 15 frames (increased from default)
// Recalculate grid dimensions based on floor size
var floorWidth = 1800; // Actual playable width
var floorHeight = 2400; // Actual playable height
self.gridWidth = Math.floor(floorWidth / self.gridSize); // ~22 cells wide
self.gridHeight = Math.floor(floorHeight / self.gridSize); // ~30 cells tall
// Create floor background - the actual playable area
self.floor = self.attachAsset('suelo', {
anchorX: 0.5,
anchorY: 0.5
});
self.floor.x = 1024; // Center horizontally
self.floor.y = 1366; // Center vertically
// Scale floor to exact playable dimensions
self.floor.scaleX = floorWidth / 500; // Scale to desired width
self.floor.scaleY = floorHeight / 500; // Scale to desired height
// Ensure floor appears below snake segments
self.floor.zIndex = -1;
// Calculate floor boundaries for proper wall placement
var floorLeft = self.floor.x - floorWidth / 2;
var floorRight = self.floor.x + floorWidth / 2;
var floorTop = self.floor.y - floorHeight / 2;
var floorBottom = self.floor.y + floorHeight / 2;
// Create four walls using pared_de_minijuego_4 - positioned exactly at floor edges
// Top wall
self.topWall = self.attachAsset('pared_de_minijuego_4', {
anchorX: 0.5,
anchorY: 0.5
});
self.topWall.x = 1024;
self.topWall.y = floorTop - 50; // Position above floor
self.topWall.scaleX = (floorWidth + 200) / 73; // Scale to cover full width plus wall thickness
self.topWall.scaleY = 1;
// Bottom wall
self.bottomWall = self.attachAsset('pared_de_minijuego_4', {
anchorX: 0.5,
anchorY: 0.5
});
self.bottomWall.x = 1024;
self.bottomWall.y = floorBottom + 50; // Position below floor
self.bottomWall.scaleX = (floorWidth + 200) / 73; // Scale to cover full width plus wall thickness
self.bottomWall.scaleY = 1;
// Left wall
self.leftWall = self.attachAsset('pared_de_minijuego_4', {
anchorX: 0.5,
anchorY: 0.5
});
self.leftWall.x = floorLeft - 50; // Position left of floor
self.leftWall.y = 1366;
self.leftWall.rotation = Math.PI / 2; // Rotate 90 degrees
self.leftWall.scaleX = (floorHeight + 200) / 73; // Scale to cover full height plus wall thickness
self.leftWall.scaleY = 1;
// Right wall
self.rightWall = self.attachAsset('pared_de_minijuego_4', {
anchorX: 0.5,
anchorY: 0.5
});
self.rightWall.x = floorRight + 50; // Position right of floor
self.rightWall.y = 1366;
self.rightWall.rotation = Math.PI / 2; // Rotate 90 degrees
self.rightWall.scaleX = (floorHeight + 200) / 73; // Scale to cover full height plus wall thickness
self.rightWall.scaleY = 1;
// Place food and create visuals
self.foodPosition = self.placeFood();
self.createVisuals();
};
// Render game - destroys and recreates all visuals to ensure top layer
self.renderGame = function () {
// STEP 1: DESTROY OLD SPRITES
if (self.foodSprite) {
self.foodSprite.destroy();
self.foodSprite = null;
}
for (var i = 0; i < self.gusanoSprites.length; i++) {
self.gusanoSprites[i].destroy();
}
self.gusanoSprites = [];
// STEP 2: DRAW FOOD (drawn first)
if (self.foodPosition) {
var foodPosPix = self.getPixelPosition(self.foodPosition.x, self.foodPosition.y);
self.foodSprite = LK.getAsset('goldenMemory', {
anchorX: 0.5,
anchorY: 0.5
});
self.foodSprite.x = foodPosPix.x;
self.foodSprite.y = foodPosPix.y;
self.foodSprite.width = self.gridSize;
self.foodSprite.height = self.gridSize;
self.foodSprite.tint = 0xFF0000; // Red apple/food
self.addChild(self.foodSprite);
}
// STEP 3: DRAW SNAKE (drawn last, appears on top)
for (var i = 0; i < self.snake.length; i++) {
var segmentPosPix = self.getPixelPosition(self.snake[i].x, self.snake[i].y);
// Use green snake segments
var sprite = LK.getAsset('gusano_minijuego4', {
anchorX: 0.5,
anchorY: 0.5
});
sprite.x = segmentPosPix.x;
sprite.y = segmentPosPix.y;
sprite.width = self.gridSize;
sprite.height = self.gridSize;
sprite.tint = 0x00FF00; // Bright green for snake
self.addChild(sprite);
self.gusanoSprites.push(sprite);
}
};
// Create visual elements
self.createVisuals = function () {
// Use renderGame method for consistent rendering
self.renderGame();
};
// Update visuals
self.updateVisuals = function () {
// Use renderGame method for consistent rendering
self.renderGame();
};
// Check game over conditions
self.checkGameOver = function (headPosition) {
// Check wall collision
if (headPosition.x < 0 || headPosition.x >= self.gridWidth || headPosition.y < 0 || headPosition.y >= self.gridHeight) {
return true;
}
// Check self collision
for (var i = 0; i < self.snake.length; i++) {
if (self.snake[i].x === headPosition.x && self.snake[i].y === headPosition.y) {
return true;
}
}
return false;
};
// Main update loop
self.update = function () {
// Control movement speed
self.moveTimer++;
if (self.moveTimer < self.moveInterval) {
return;
}
self.moveTimer = 0;
// Update direction
self.direction = self.nextDirection;
// Calculate new head position
var head = self.snake[0];
var newHead = {
x: head.x,
y: head.y
};
if (self.direction === 'up') {
newHead.y -= 1;
} else if (self.direction === 'down') {
newHead.y += 1;
} else if (self.direction === 'left') {
newHead.x -= 1;
} else if (self.direction === 'right') {
newHead.x += 1;
}
// Check game over
if (self.checkGameOver(newHead)) {
LK.showGameOver();
return;
}
// Add new head
self.snake.unshift(newHead);
// Check food consumption
if (newHead.x === self.foodPosition.x && newHead.y === self.foodPosition.y) {
// Eat food - snake grows, place new food
LK.setScore(LK.getScore() + 10);
// Place new food (visuals will be handled by renderGame)
self.foodPosition = self.placeFood();
LK.getSound('collect').play();
} else {
// Normal movement - remove tail
self.snake.pop();
}
// Render all visuals (final step - ensures snake appears on top)
self.renderGame();
};
// Keyboard controls are handled by the global keyDown event listener
// No mouse click handling needed for Snake Game
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
var selectedEmotion = storage.selectedEmotion || 'joy';
var gameStarted = false;
var countdownActive = false;
var countdownValue = 3;
var character = null;
var goldenMemories = [];
var corruptedMemories = [];
var spawnTimer = 0;
var difficultyLevel = 1;
var health = 100;
var maxHealth = 100;
// Game state management
var gameState = 'menu'; // 'menu', 'levelSelection', 'characterSelection', 'countdown', 'playing'
var currentLevel = storage.currentLevel || 1;
var levelBoxes = [];
var characterSelectionActive = false;
var selectionButtons = [];
// Timer variables
var gameTimer = 223; // 223 seconds
var timerActive = false;
var speedIncreased = false;
// Character pricing and spheres
var totalSpheres = storage.totalSpheres || 0;
var attemptCount = storage.attemptCount || 0;
// Purchase limits for shop items (max 20 each)
var purchaseCounts = storage.purchaseCounts || {
sphere_multiplier: 0,
speed_multiplier: 0,
health_bonus: 0
};
var characterPrices = {
joy: 0,
// Free
anger: 60,
disgust: 120,
fear: 180,
tristesa: 240
};
var ownedCharacters = storage.ownedCharacters || ['joy']; // Joy is free by default
// Level-specific attempt tracking
var levelAttempts = storage.levelAttempts || {};
// All levels are unlocked by default, no need to track unlocked levels
// Speed boost variables for level 1
var speedBoostActive = false;
var speedBoostTimer = 0;
// Upgrade system variables
var sphereMultiplier = storage.sphereMultiplier || 1;
var speedMultiplier = storage.speedMultiplier || 1;
var maxHealthBonus = storage.maxHealthBonus || 0;
var shopItems = [];
// Shop timer variables
var shopTimer = 10;
var shopTimerActive = false;
var shopTimerText = null;
var shopTimerInterval = null;
// Level 2 specific variables - Flappy Bird style
var sideSpikes = [];
var birdVelocity = 0;
var gravity = 0.8;
var flapStrength = -12;
var pipes = [];
var pipeGap = 400;
var pipeWidth = 100;
var distanceTraveled = 0;
var scoreCounter = 0;
// Level 3 specific variables - Bubble Shooter
var bubbleShooter = null;
var aimAngle = 0;
var aimGuide = [];
var shootPower = 15;
// Missed shots counter for Level 3
var missedShotsText = new Text2('Fallos: 0/5', {
size: 50,
fill: 0xFFFFFF
});
missedShotsText.anchor.set(1, 1);
missedShotsText.alpha = 0; // Hide initially
LK.gui.bottomRight.addChild(missedShotsText);
var heightText = new Text2('Distancia: 0m', {
size: 50,
fill: 0xFFFFFF
});
heightText.anchor.set(0, 0);
LK.gui.bottomLeft.addChild(heightText);
heightText.y = 100;
// Add invisible collision box to heightText
var heightTextBox = LK.getAsset('level1', {
anchorX: 0,
anchorY: 0,
scaleX: 1.5,
scaleY: 0.5
});
heightTextBox.alpha = 0;
heightTextBox.x = 0;
heightTextBox.y = 100;
heightTextBox.textElement = heightText;
LK.gui.bottomLeft.addChild(heightTextBox);
// heightText no longer auto-hides
// UI Elements
var scoreText = new Text2('Puntos: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
scoreText.alpha = 0; // Hide initially
LK.gui.top.addChild(scoreText);
// Add invisible collision box to scoreText
var scoreTextBox = LK.getAsset('level1', {
anchorX: 0.5,
anchorY: 0,
scaleX: 1.5,
scaleY: 0.6
});
scoreTextBox.alpha = 0;
scoreTextBox.x = 0;
scoreTextBox.y = 0;
scoreTextBox.textElement = scoreText;
LK.gui.top.addChild(scoreTextBox);
// scoreText no longer auto-hides
var healthText = new Text2('Vida: 100/100', {
size: 50,
fill: 0xFF0000
});
healthText.anchor.set(1, 0);
LK.gui.topRight.addChild(healthText);
// Add invisible collision box to healthText
var healthTextBox = LK.getAsset('level1', {
anchorX: 1,
anchorY: 0,
scaleX: 1.2,
scaleY: 0.5
});
healthTextBox.alpha = 0;
healthTextBox.x = 0;
healthTextBox.y = 0;
healthTextBox.textElement = healthText;
LK.gui.topRight.addChild(healthTextBox);
// healthText no longer auto-hides
// Timer display
var timerText = new Text2('Tiempo: 223', {
size: 50,
fill: 0xFFFFFF
});
timerText.anchor.set(0, 1);
LK.gui.bottomLeft.addChild(timerText);
// Add invisible collision box to timerText
var timerTextBox = LK.getAsset('level1', {
anchorX: 0,
anchorY: 1,
scaleX: 1.2,
scaleY: 0.5
});
timerTextBox.alpha = 0;
timerTextBox.x = 0;
timerTextBox.y = 0;
timerTextBox.textElement = timerText;
LK.gui.bottomLeft.addChild(timerTextBox);
// timerText no longer auto-hides
// Sphere counter text (moved to where energy bar was)
var sphereCountText = new Text2('Esferas: ' + totalSpheres, {
size: 40,
fill: 0xFFD700
});
sphereCountText.anchor.set(1, 0);
sphereCountText.x = -20;
sphereCountText.y = 80;
LK.gui.topRight.addChild(sphereCountText);
// Add invisible collision box to sphereCountText
var sphereCountTextBox = LK.getAsset('level1', {
anchorX: 1,
anchorY: 0,
scaleX: 1.2,
scaleY: 0.5
});
sphereCountTextBox.alpha = 0;
sphereCountTextBox.x = -20;
sphereCountTextBox.y = 80;
sphereCountTextBox.textElement = sphereCountText;
LK.gui.topRight.addChild(sphereCountTextBox);
// sphereCountText no longer auto-hides
// Menu button - rectangular button to return to level selection (moved to bottom right)
var menuButton = LK.getAsset('level1', {
anchorX: 1,
anchorY: 1,
scaleX: 0.8,
scaleY: 0.6
});
menuButton.x = 2048 - 50;
menuButton.y = 2732 - 50;
menuButton.tint = 0x333333;
menuButton.alpha = 0; // Hide initially
game.addChild(menuButton);
var menuButtonText = new Text2('MENU', {
size: 50,
fill: 0xFFFFFF
});
menuButtonText.anchor.set(0.5, 0.5);
menuButtonText.x = 2048 - 50 - 240 / 2; // Center horizontally within button
menuButtonText.y = 2732 - 50 - 120 / 2; // Center vertically within button
menuButtonText.alpha = 0; // Hide initially
game.addChild(menuButtonText);
// Main Menu UI
var titleText = new Text2('INTENSAMENTE', {
size: 120,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 800;
game.addChild(titleText);
// Add invisible collision box to titleText
var titleTextBox = LK.getAsset('level1', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 0.8
});
titleTextBox.alpha = 0;
titleTextBox.x = titleText.x;
titleTextBox.y = titleText.y;
titleTextBox.textElement = titleText;
game.addChild(titleTextBox);
// titleText no longer auto-hides
var playButton = new Text2('JUGAR', {
size: 80,
fill: 0x00FF00
});
playButton.anchor.set(0.5, 0.5);
playButton.x = 1024;
playButton.y = 1200;
game.addChild(playButton);
// Add invisible collision box to playButton
var playButtonBox = LK.getAsset('level1', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.8
});
playButtonBox.alpha = 0;
playButtonBox.x = playButton.x;
playButtonBox.y = playButton.y;
playButtonBox.textElement = playButton;
game.addChild(playButtonBox);
// playButton no longer auto-hides
var instructionText = new Text2('Selecciona tu personaje', {
size: 80,
fill: 0xFFFFFF
});
instructionText.anchor.set(0.5, 0.5);
instructionText.x = 1024;
instructionText.y = 600;
instructionText.alpha = 0;
instructionText.visible = false;
game.addChild(instructionText);
// Add invisible collision box to instructionText
var instructionTextBox = LK.getAsset('level1', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 0.8
});
instructionTextBox.alpha = 0;
instructionTextBox.x = instructionText.x;
instructionTextBox.y = instructionText.y;
instructionTextBox.textElement = instructionText;
game.addChild(instructionTextBox);
// instructionText no longer auto-hides
instructionText.showWithTimeout = function () {
instructionText.alpha = 1;
instructionText.visible = true;
};
var countdownText = new Text2('3', {
size: 200,
fill: 0xFFFFFF
});
countdownText.anchor.set(0.5, 0.5);
countdownText.x = 1024;
countdownText.y = 1366;
countdownText.alpha = 0;
countdownText.visible = false;
game.addChild(countdownText);
// Add invisible collision box to countdownText
var countdownTextBox = LK.getAsset('level1', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.2
});
countdownTextBox.alpha = 0;
countdownTextBox.x = countdownText.x;
countdownTextBox.y = countdownText.y;
countdownTextBox.textElement = countdownText;
game.addChild(countdownTextBox);
// countdownText no longer auto-hides
countdownText.showWithTimeout = function () {
countdownText.alpha = 1;
countdownText.visible = true;
};
// Spawn static character selection spheres in center
function spawnCharacterSelectionSpheres() {
// Only spawn once
if (goldenMemories.length === 0) {
var emotions = [{
type: 'joy',
name: 'ALEGRÍA',
color: 0xffeb3b,
x: 524,
y: 800
}, {
type: 'anger',
name: 'ENOJO',
color: 0xf44336,
x: 1524,
y: 800
}, {
type: 'disgust',
name: 'DESAGRADO',
color: 0x4caf50,
x: 524,
y: 1200
}, {
type: 'fear',
name: 'MIEDO',
color: 0x9c27b0,
x: 1024,
y: 1200
}, {
type: 'tristesa',
name: 'TRISTEZA',
color: 0x2196f3,
x: 1524,
y: 1200
}];
// Hide score text in character selection
scoreText.alpha = 0;
// Add spheres counter display only if not lvl1
if (currentLevel !== 1) {
var spheresText = new Text2('Esferas: ' + totalSpheres, {
size: 60,
fill: 0xFFD700
});
spheresText.anchor.set(0.5, 0.5);
spheresText.x = 1024;
spheresText.y = 500;
game.addChild(spheresText);
}
for (var i = 0; i < emotions.length; i++) {
var emotionData = emotions[i];
// Create character image instead of sphere
var characterImage = LK.getAsset(emotionData.type, {
anchorX: 0.5,
anchorY: 0.5
});
characterImage.x = emotionData.x;
characterImage.y = emotionData.y;
characterImage.characterType = emotionData.type;
game.addChild(characterImage);
goldenMemories.push(characterImage);
// Check if character is owned or affordable
var isOwned = ownedCharacters.indexOf(emotionData.type) !== -1;
var canAfford = totalSpheres >= characterPrices[emotionData.type];
var price = characterPrices[emotionData.type];
// Dim character if not owned and can't afford
if (!isOwned && !canAfford) {
characterImage.alpha = 0.3;
}
// Add text label with price
var labelText = emotionData.name;
if (!isOwned) {
labelText += '\nPrecio: ' + price + ' esferas';
} else {
labelText += '\nDISPONIBLE';
}
var nameText = new Text2(labelText, {
size: 35,
fill: isOwned ? 0x00FF00 : canAfford ? 0xFFFFFF : 0xFF6666
});
nameText.anchor.set(0.5, 0.5);
nameText.x = emotionData.x;
nameText.y = emotionData.y + 150;
game.addChild(nameText);
characterImage.nameText = nameText;
characterImage.isOwned = isOwned;
characterImage.canAfford = canAfford;
}
}
}
// Handle sphere selection for character choice
function handleSphereSelection(sphere) {
if (sphere.characterType) {
var characterType = sphere.characterType;
var isOwned = ownedCharacters.indexOf(characterType) !== -1;
var canAfford = totalSpheres >= characterPrices[characterType];
var price = characterPrices[characterType];
// Check if character can be selected
if (!isOwned && !canAfford) {
// Flash red to indicate can't select
LK.effects.flashObject(sphere, 0xFF0000, 500);
// Play error sound
LK.getSound('error').play();
// Show insufficient spheres message
var errorText = new Text2('No tienes esferas suficientes', {
size: 60,
fill: 0xFF0000
});
errorText.anchor.set(0.5, 0.5);
errorText.x = 1024;
errorText.y = 2200;
game.addChild(errorText);
// Remove error message after 2 seconds
LK.setTimeout(function () {
errorText.destroy();
}, 2000);
return;
}
// If not owned but can afford, purchase the character
if (!isOwned && canAfford) {
totalSpheres -= price;
ownedCharacters.push(characterType);
storage.totalSpheres = totalSpheres;
storage.ownedCharacters = ownedCharacters;
// Update spheres display after purchase
var spheresTextElements = [];
for (var k = 0; k < game.children.length; k++) {
if (game.children[k].text && game.children[k].text.indexOf('Esferas:') === 0) {
game.children[k].setText('Esferas: ' + totalSpheres);
break;
}
}
} else if (isOwned) {
// Character is already owned, no need to purchase again
} else {
// Character not owned and can't afford - deduct price anyway and add to owned
totalSpheres -= price;
if (totalSpheres < 0) totalSpheres = 0; // Prevent negative spheres
ownedCharacters.push(characterType);
storage.totalSpheres = totalSpheres;
storage.ownedCharacters = ownedCharacters;
// Update spheres display after purchase
for (var k = 0; k < game.children.length; k++) {
if (game.children[k].text && game.children[k].text.indexOf('Esferas:') === 0) {
game.children[k].setText('Esferas: ' + totalSpheres);
break;
}
}
}
selectedEmotion = characterType;
storage.selectedEmotion = selectedEmotion;
// Flash selected sphere
LK.effects.flashObject(sphere, 0xFFFFFF, 500);
// Clear all spheres and their labels
for (var i = goldenMemories.length - 1; i >= 0; i--) {
if (goldenMemories[i].nameText) {
goldenMemories[i].nameText.destroy();
}
goldenMemories[i].destroy();
goldenMemories.splice(i, 1);
}
// Instruction text already removed, no hiding needed
// Go to shop before starting game
LK.setTimeout(function () {
createShop();
}, 600);
}
}
// Create level selection UI
function createLevelSelection() {
gameState = 'levelSelection';
// Hide menu elements
titleText.alpha = 0;
playButton.alpha = 0;
// Level selection title removed to avoid interference
// Create level boxes in a grid (2 columns, 5 rows)
var levelNames = ['MINIJUEGO 1', 'MINIJUEGO 2', 'MINIJUEGO 3', 'MINIJUEGO 4', 'MINIJUEGO 5', 'MINIJUEGO 6', 'MINIJUEGO 7', 'MINIJUEGO 8', 'MINIJUEGO 9', 'MINIJUEGO 10'];
var levelAssets = ['level1', 'level2', 'level3', 'level4', 'level5', 'level6', 'level7', 'level8', 'level9', 'level10'];
for (var i = 0; i < 10; i++) {
var col = i % 2;
var row = Math.floor(i / 2);
var levelBox = LK.getAsset(levelAssets[i], {
anchorX: 0.5,
anchorY: 0.5
});
levelBox.x = 600 + col * 848; // 600 and 1448 for two columns
levelBox.y = 700 + row * 280; // Spacing between rows
levelBox.levelNumber = i + 1;
levelBox.levelName = levelNames[i];
game.addChild(levelBox);
levelBoxes.push(levelBox);
// All levels are unlocked
var isUnlocked = true;
// No dimming since all levels are unlocked
// Add level text without lock status
var levelText = new Text2(levelNames[i], {
size: 50,
fill: 0xFFFFFF
});
levelText.anchor.set(0.5, 0.5);
levelText.x = levelBox.x;
levelText.y = levelBox.y;
game.addChild(levelText);
levelBox.levelText = levelText;
levelBox.isUnlocked = isUnlocked;
}
}
// Create shop screen
function createShop() {
gameState = 'shop';
// Hide other UI elements
titleText.alpha = 0;
playButton.alpha = 0;
instructionText.alpha = 0;
// Shop title
var shopTitle = new Text2('TIENDA DE MEJORAS', {
size: 120,
fill: 0xFFD700
});
shopTitle.anchor.set(0.5, 0.5);
shopTitle.x = 1024;
shopTitle.y = 400;
game.addChild(shopTitle);
// Show current spheres
var spheresDisplay = new Text2('Esferas: ' + totalSpheres, {
size: 80,
fill: 0xFFFFFF
});
spheresDisplay.anchor.set(0.5, 0.5);
spheresDisplay.x = 1024;
spheresDisplay.y = 500;
game.addChild(spheresDisplay);
// Generate shop items with fixed 4 slots (no rarity system)
var shopItemsData = [];
// Calculate centered positions for 3 shop items
var itemWidth = 300; // Width of each shop item box (scaled)
var itemSpacing = 150; // Spacing between items
var totalWidth = 3 * itemWidth + 2 * itemSpacing; // Total width of all items plus spacing
var startX = (2048 - totalWidth) / 2 + itemWidth / 2; // Center the entire layout
// Slot 1: Multiplicador de Esferas (Fixed)
var sphereMultItem = {
name: 'Multiplicador\nde Esferas',
description: 'x' + (sphereMultiplier + 0.2).toFixed(1),
price: 200 + Math.floor(100 * purchaseCounts.sphere_multiplier),
type: 'sphere_multiplier',
x: startX,
y: 700
};
// Slot 2: Multiplicador de Velocidad (Fixed)
var speedMultItem = {
name: 'Multiplicador\nde Velocidad',
description: '+5% Velocidad',
price: 111 + Math.floor(60 * purchaseCounts.speed_multiplier),
type: 'speed_multiplier',
x: startX + itemWidth + itemSpacing,
y: 700
};
// Slot 3: Aumento de Vida Max (Fixed)
var healthBonusItem = {
name: 'Aumento\nde Vida Max',
description: '+5 Vida Max',
price: 91 + Math.floor(50 * purchaseCounts.health_bonus),
type: 'health_bonus',
x: startX + 2 * (itemWidth + itemSpacing),
y: 700
};
shopItemsData.push(sphereMultItem);
shopItemsData.push(speedMultItem);
shopItemsData.push(healthBonusItem);
for (var i = 0; i < shopItemsData.length; i++) {
var itemData = shopItemsData[i];
// Create item box (expanded)
var itemBox = LK.getAsset('level1', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
itemBox.x = itemData.x;
itemBox.y = itemData.y;
itemBox.itemType = itemData.type;
itemBox.price = itemData.price;
itemBox.tint = 0x00FF00;
game.addChild(itemBox);
shopItems.push(itemBox);
// Item name
var nameText = new Text2(itemData.name, {
size: 55,
fill: 0xFFFFFF
});
nameText.anchor.set(0.5, 0.5);
nameText.x = itemData.x;
nameText.y = itemData.y - 40;
game.addChild(nameText);
itemBox.nameText = nameText;
// Item price
var priceText = new Text2('Precio: ' + itemData.price, {
size: 50,
fill: 0xFFFFFF
});
priceText.anchor.set(0.5, 0.5);
priceText.x = itemData.x;
priceText.y = itemData.y + 40;
game.addChild(priceText);
itemBox.priceText = priceText;
}
// Exit button (changed from Continue)
var exitBtn = new Text2('SALIR', {
size: 80,
fill: 0x00FF00
});
exitBtn.anchor.set(0.5, 0.5);
exitBtn.x = 1024;
exitBtn.y = 1400; // Moved down by 200px
game.addChild(exitBtn);
shopItems.push(exitBtn);
exitBtn.isContinueBtn = true;
// Skip button
var skipBtn = new Text2('SALTAR', {
size: 80,
fill: 0x00FF00
});
skipBtn.anchor.set(0.5, 0.5);
skipBtn.x = 1024;
skipBtn.y = 1500; // Moved down by 200px
game.addChild(skipBtn);
shopItems.push(skipBtn);
skipBtn.isSkipBtn = true;
// Shop timer display
shopTimerText = new Text2('Tiempo: 10', {
size: 80,
fill: 0xFF0000
});
shopTimerText.anchor.set(0.5, 0.5);
shopTimerText.x = 1024;
shopTimerText.y = 1200; // Moved up to make room for buttons
game.addChild(shopTimerText);
shopItems.push(shopTimerText);
// Start shop timer
shopTimer = 10;
shopTimerActive = true;
shopTimerInterval = LK.setInterval(function () {
shopTimer--;
shopTimerText.setText('Tiempo: ' + shopTimer);
if (shopTimer <= 0) {
LK.clearInterval(shopTimerInterval);
shopTimerActive = false;
// Auto-proceed when timer expires
// Clear shop UI
for (var j = shopItems.length - 1; j >= 0; j--) {
if (shopItems[j].nameText) shopItems[j].nameText.destroy();
if (shopItems[j].priceText) shopItems[j].priceText.destroy();
shopItems[j].destroy();
}
shopItems = [];
// Clear shop title and spheres display
for (var k = game.children.length - 1; k >= 0; k--) {
if (game.children[k].text && (game.children[k].text.indexOf('TIENDA') === 0 || game.children[k].text.indexOf('Esferas:') === 0)) {
tween(game.children[k], {
alpha: 0
}, {
duration: 1000
});
LK.setTimeout(function (element) {
return function () {
if (element && element.destroy) {
element.destroy();
}
};
}(game.children[k]), 1000);
}
}
// Go to countdown
startCountdown();
}
}, 1000);
}
// Start character selection
function startCharacterSelection() {
gameState = 'characterSelection';
// Hide menu elements
titleText.alpha = 0;
playButton.alpha = 0;
// Character selection instruction text removed to avoid interference
// Spawn character selection spheres
spawnCharacterSelectionSpheres();
}
// Start countdown before game
function startCountdown() {
gameState = 'countdown';
countdownActive = true;
countdownValue = 3;
countdownText.showWithTimeout();
countdownText.setText('3');
var countdownInterval = LK.setInterval(function () {
countdownValue--;
if (countdownValue > 0) {
countdownText.setText(countdownValue.toString());
} else if (countdownValue === 0) {
countdownText.setText('¡Juega!');
} else {
LK.clearInterval(countdownInterval);
countdownText.alpha = 0;
countdownText.visible = false;
startActualGame();
}
}, 1000);
}
// Start the actual game
function startActualGame() {
gameState = 'playing';
countdownActive = false;
gameStarted = true;
// Initialize timer
gameTimer = 223;
timerActive = true;
speedIncreased = false;
// Show menu button during gameplay
menuButton.alpha = 1;
menuButtonText.alpha = 1;
// Hide all level selection boxes during gameplay
for (var i = 0; i < levelBoxes.length; i++) {
levelBoxes[i].alpha = 0;
if (levelBoxes[i].levelText) {
levelBoxes[i].levelText.alpha = 0;
}
}
// Hide any shop elements that might still be visible during gameplay
for (var shopCleanup = game.children.length - 1; shopCleanup >= 0; shopCleanup--) {
if (game.children[shopCleanup].text && (game.children[shopCleanup].text.indexOf('TIENDA') === 0 || game.children[shopCleanup].text.indexOf('Esferas:') === 0 && game.children[shopCleanup] !== sphereCountText)) {
game.children[shopCleanup].alpha = 0;
game.children[shopCleanup].visible = false;
}
}
// Hide all shop-related elements during active gameplay
for (var shopCleanup = game.children.length - 1; shopCleanup >= 0; shopCleanup--) {
var child = game.children[shopCleanup];
if (child.text && (child.text.indexOf('TIENDA DE MEJORAS') === 0 || child.text.indexOf('Esferas:') === 0 && child !== sphereCountText)) {
child.alpha = 0;
child.visible = false;
}
}
// Show/hide UI elements based on level
if (currentLevel === 1) {
// Level 1: Show health, score, timer, sphere counter
scoreText.alpha = 1;
healthText.alpha = 1;
timerText.alpha = 1;
heightText.alpha = 0;
sphereCountText.alpha = 1;
} else if (currentLevel === 2) {
// Level 2: Show only distance and score
scoreText.alpha = 1;
healthText.alpha = 0;
timerText.alpha = 0;
heightText.alpha = 1;
sphereCountText.alpha = 0; // sphereCountText no longer auto-hides
} else if (currentLevel === 3) {
// Level 3: Show score and missed shots counter
scoreText.alpha = 1;
healthText.alpha = 0;
timerText.alpha = 0;
heightText.alpha = 0;
sphereCountText.alpha = 0;
missedShotsText.alpha = 1;
} else if (currentLevel === 4) {
// Level 4: Show only score
scoreText.alpha = 1;
healthText.alpha = 0;
timerText.alpha = 0;
heightText.alpha = 0;
sphereCountText.alpha = 0;
missedShotsText.alpha = 0;
} else {
// Other levels: Show score and sphere counter
scoreText.alpha = 1;
healthText.alpha = 0;
timerText.alpha = 0;
heightText.alpha = 0;
sphereCountText.alpha = 1;
}
// Create player character
if (currentLevel === 3) {
// Level 3: Create bubble shooter instead of character
bubbleShooter = new BubbleShooter();
bubbleShooter.initGrid();
bubbleShooter.createInitialBubbles();
game.addChild(bubbleShooter);
// Create cannon and add to bubbleShooter container
var cannon = LK.getAsset('cannon', {
anchorX: 0.5,
anchorY: 1
});
cannon.x = 1024;
cannon.y = 2600;
bubbleShooter.cannon = cannon;
bubbleShooter.addChild(cannon); // Add cannon to bubbleShooter instead of game
// Create initial bubbles after setting up cannon
bubbleShooter.createNewBubble();
} else if (currentLevel === 4) {
// Level 4: Create Snake Game
var snakeGame = new SnakeGame();
snakeGame.initGame();
game.addChild(snakeGame);
// Store reference for input handling
game.snakeGame = snakeGame;
} else {
character = new EmotionCharacter(selectedEmotion);
if (currentLevel === 2) {
// Spawn in middle of screen for Flappy Bird level
character.x = 1024;
character.y = 1366; // Middle of screen vertically
// Reset level 2 specific variables
birdVelocity = 0;
distanceTraveled = 0;
scoreCounter = 0;
// Clear any existing pipes
for (var p = pipes.length - 1; p >= 0; p--) {
pipes[p].destroy();
}
pipes = [];
} else {
character.x = 1024;
character.y = 2400;
// Reset level 1 specific variables with upgrades
health = 100 + maxHealthBonus;
maxHealth = 100 + maxHealthBonus;
healthText.setText('Vida: ' + health + '/' + maxHealth);
// Clear any existing memories
for (var g = goldenMemories.length - 1; g >= 0; g--) {
goldenMemories[g].destroy();
}
goldenMemories = [];
for (var c = corruptedMemories.length - 1; c >= 0; c--) {
corruptedMemories[c].destroy();
}
corruptedMemories = [];
}
game.addChild(character);
}
// Start level-specific background music
if (currentLevel === 2) {
LK.playMusic('musica_de_nivel_2');
} else {
LK.playMusic('gameMusic');
}
}
// Spawn memory function
function spawnMemory() {
if (gameState === 'characterSelection') {
// Character selection phase - spawn static character spheres in center
spawnCharacterSelectionSpheres();
} else if (gameState === 'playing') {
// Game phase - spawn colored spheres and corrupted ones
var spawnX = Math.random() * (2048 - 160) + 80;
var randomType = Math.random();
// Define sphere colors for optimization
var sphereColors = [0xffeb3b, 0x4caf50, 0x2196f3, 0xf44336];
var colorThresholds = [0.15, 0.3, 0.45, 0.6];
// Calculate time-based spine multiplier - more spines as time progresses
var initialTimer = 223;
var timeElapsed = initialTimer - gameTimer;
var timeProgressRatio = Math.min(timeElapsed / initialTimer, 1); // 0 to 1 progression
// Base spine spawn probability increases with time (starts at 0.33, goes up to 0.85)
var baseSpineProbability = 0.33 + timeProgressRatio * 0.52;
// Check for golden memory creation - probability decreases over time to allow for more spines
var goldenProbability = 0.67 * (1 - timeProgressRatio * 0.6); // Decreases from 0.67 to ~0.27
var createGolden = false;
var tintColor = 0xffeb3b;
for (var i = 0; i < colorThresholds.length; i++) {
if (randomType < colorThresholds[i] * goldenProbability) {
createGolden = true;
tintColor = sphereColors[i];
break;
}
}
if (createGolden) {
var memory = new GoldenMemory();
memory.x = spawnX;
memory.y = -100;
memory.lastY = memory.y;
memory.children[0].tint = tintColor;
goldenMemories.push(memory);
game.addChild(memory);
} else {
// Corrupted memory with spikes - spawn rate increases dramatically over time
var corrupted = new CorruptedMemory();
corrupted.x = spawnX;
corrupted.y = -100;
corrupted.lastY = corrupted.y;
corruptedMemories.push(corrupted);
game.addChild(corrupted);
}
}
}
// Game event handlers
game.down = function (x, y, obj) {
if (gameState === 'menu') {
// Check if JUGAR button was clicked with invisible hitbox
var playButtonX = playButton.x - playButton.width / 2;
var playButtonY = playButton.y - playButton.height / 2;
var playButtonWidth = playButton.width;
var playButtonHeight = playButton.height;
if (x >= playButtonX && x <= playButtonX + playButtonWidth && y >= playButtonY && y <= playButtonY + playButtonHeight) {
createLevelSelection();
}
} else if (gameState === 'levelSelection') {
// Check if any level box was clicked
for (var i = 0; i < levelBoxes.length; i++) {
var levelBox = levelBoxes[i];
var boxX = levelBox.x - 150; // Half of width (300/2)
var boxY = levelBox.y - 100; // Half of height (200/2)
var boxWidth = 300;
var boxHeight = 200;
if (x >= boxX && x <= boxX + boxWidth && y >= boxY && y <= boxY + boxHeight) {
// All levels are unlocked - no need to check
// Flash selected level
LK.effects.flashObject(levelBox, 0xFFFFFF, 500);
// Set current level
currentLevel = levelBox.levelNumber;
storage.currentLevel = currentLevel;
// Clear level selection UI
for (var j = levelBoxes.length - 1; j >= 0; j--) {
if (levelBoxes[j].levelText) {
levelBoxes[j].levelText.destroy();
}
levelBoxes[j].destroy();
}
levelBoxes = [];
// Level selection title already removed, no cleanup needed
// Start character selection after short delay
LK.setTimeout(function () {
startCharacterSelection();
}, 600);
break;
}
}
} else if (gameState === 'shop') {
// Handle shop item clicks
for (var i = 0; i < shopItems.length; i++) {
var item = shopItems[i];
var itemX = item.x - 150;
var itemY = item.y - 100;
var itemWidth = 300;
var itemHeight = 200;
if (x >= itemX && x <= itemX + itemWidth && y >= itemY && y <= itemY + itemHeight) {
if (item.isContinueBtn || item.isSkipBtn) {
// Clear shop timer
if (shopTimerInterval) {
LK.clearInterval(shopTimerInterval);
shopTimerActive = false;
}
// Clear shop UI
for (var j = shopItems.length - 1; j >= 0; j--) {
if (shopItems[j].nameText) shopItems[j].nameText.destroy();
if (shopItems[j].priceText) shopItems[j].priceText.destroy();
shopItems[j].destroy();
}
shopItems = [];
// Clear shop title and spheres display
for (var k = game.children.length - 1; k >= 0; k--) {
if (game.children[k].text && (game.children[k].text.indexOf('TIENDA') === 0 || game.children[k].text.indexOf('Esferas:') === 0)) {
game.children[k].destroy();
}
}
if (item.isSkipBtn) {
// Skip shop timer and go directly to countdown
startCountdown();
} else {
// Exit button - go directly to countdown
startCountdown();
}
break;
} else if (totalSpheres >= item.price) {
// Check purchase limit
if (purchaseCounts[item.itemType] >= 20) {
// Show max purchases message
var maxText = new Text2('Máximo de compras alcanzado', {
size: 60,
fill: 0xFF0000
});
maxText.anchor.set(0.5, 0.5);
maxText.x = 1024;
maxText.y = 1000;
game.addChild(maxText);
LK.setTimeout(function () {
maxText.destroy();
}, 2000);
LK.getSound('error').play();
} else {
// Purchase item
totalSpheres -= item.price;
storage.totalSpheres = totalSpheres;
purchaseCounts[item.itemType]++;
storage.purchaseCounts = purchaseCounts;
if (item.itemType === 'sphere_multiplier') {
sphereMultiplier += 0.2;
storage.sphereMultiplier = sphereMultiplier;
} else if (item.itemType === 'speed_multiplier') {
speedMultiplier += 0.05;
storage.speedMultiplier = speedMultiplier;
} else if (item.itemType === 'health_bonus') {
maxHealthBonus += 5;
storage.maxHealthBonus = maxHealthBonus;
}
}
LK.effects.flashObject(item, 0x00FF00, 300);
LK.getSound('collect').play();
// Clear shop timer before recreating
if (shopTimerInterval) {
LK.clearInterval(shopTimerInterval);
shopTimerActive = false;
}
// Recreate shop to update prices
for (var j = shopItems.length - 1; j >= 0; j--) {
if (shopItems[j].nameText) shopItems[j].nameText.destroy();
if (shopItems[j].priceText) shopItems[j].priceText.destroy();
shopItems[j].destroy();
}
shopItems = [];
for (var k = game.children.length - 1; k >= 0; k--) {
if (game.children[k].text && (game.children[k].text.indexOf('TIENDA') === 0 || game.children[k].text.indexOf('Esferas:') === 0)) {
game.children[k].destroy();
}
}
createShop();
break;
} else {
LK.effects.flashObject(item, 0xFF0000, 300);
LK.getSound('error').play();
}
}
}
} else if (gameState === 'characterSelection') {
// Check if any character sphere was clicked with invisible hitboxes
for (var i = 0; i < goldenMemories.length; i++) {
var sphere = goldenMemories[i];
var sphereX = sphere.x - 60; // Sphere width is 120, so half is 60
var sphereY = sphere.y - 60; // Sphere height is 120, so half is 60
var sphereWidth = 120;
var sphereHeight = 120;
if (x >= sphereX && x <= sphereX + sphereWidth && y >= sphereY && y <= sphereY + sphereHeight) {
handleSphereSelection(sphere);
break;
}
}
} else if (gameState === 'playing') {
// Check if menu button was clicked (adjusted for bottom right anchor)
var menuButtonX = menuButton.x - 240; // Full scaled width (anchored to right)
var menuButtonY = menuButton.y - 120; // Full scaled height (anchored to bottom)
var menuButtonWidth = 240; // Scaled width
var menuButtonHeight = 120; // Scaled height
if (x >= menuButtonX && x <= menuButtonX + menuButtonWidth && y >= menuButtonY && y <= menuButtonY + menuButtonHeight) {
// Stop any music
LK.stopMusic();
// Hide menu button
menuButton.alpha = 0;
menuButtonText.alpha = 0;
// Hide all UI elements
scoreText.alpha = 0;
healthText.alpha = 0;
timerText.alpha = 0;
heightText.alpha = 0;
sphereCountText.alpha = 0;
// Destroy character if exists
if (character) {
character.destroy();
character = null;
}
// Clear all game objects
for (var i = goldenMemories.length - 1; i >= 0; i--) {
goldenMemories[i].destroy();
}
goldenMemories = [];
for (var i = corruptedMemories.length - 1; i >= 0; i--) {
corruptedMemories[i].destroy();
}
corruptedMemories = [];
for (var i = pipes.length - 1; i >= 0; i--) {
pipes[i].destroy();
}
pipes = [];
for (var i = sideSpikes.length - 1; i >= 0; i--) {
sideSpikes[i].destroy();
}
sideSpikes = [];
// Clean up bubble shooter
if (bubbleShooter) {
bubbleShooter.destroy();
bubbleShooter = null;
}
// Clean up snake game
if (game.snakeGame) {
game.snakeGame.destroy();
game.snakeGame = null;
}
// Hide missed shots counter
missedShotsText.alpha = 0;
// Clean up aim guide
for (var i = aimGuide.length - 1; i >= 0; i--) {
aimGuide[i].destroy();
}
aimGuide = [];
// Reset game variables
gameStarted = false;
timerActive = false;
speedIncreased = false;
// Show menu elements
titleText.alpha = 1;
playButton.alpha = 1;
// Go to level selection
createLevelSelection();
}
if (currentLevel === 2) {
// Handle flapping in lvl2 - Flappy Bird style
if (character) {
birdVelocity = flapStrength;
}
} else if (currentLevel === 3 && bubbleShooter) {
// Level 3: Bubble shooting
if (bubbleShooter.currentBubble && bubbleShooter.canShoot) {
// Get fresh aim angle at moment of shooting
var cannonX = bubbleShooter.cannon.x;
var cannonY = bubbleShooter.cannon.y;
var freshAimAngle = Math.atan2(y - cannonY, x - cannonX);
// Limit aiming angle to upward directions only (allow wider range for better shooting)
if (freshAimAngle > -Math.PI * 0.9 && freshAimAngle < -Math.PI * 0.1) {
// Use shootBubble method for proper physics
bubbleShooter.shootBubble(freshAimAngle);
}
}
} else if (currentLevel === 4 && game.snakeGame) {
// Level 4: Snake Game mouse click control
// Convert click position to grid coordinates
var floorLeft = game.snakeGame.floor.x - 1800 / 2;
var floorTop = game.snakeGame.floor.y - 2400 / 2;
var targetGridX = Math.floor((x - floorLeft) / game.snakeGame.gridSize);
var targetGridY = Math.floor((y - floorTop) / game.snakeGame.gridSize);
// Ensure target is within grid bounds
if (targetGridX >= 0 && targetGridX < game.snakeGame.gridWidth && targetGridY >= 0 && targetGridY < game.snakeGame.gridHeight) {
var currentHead = game.snakeGame.snake[0];
var dx = targetGridX - currentHead.x;
var dy = targetGridY - currentHead.y;
// Determine movement direction based on click position
// Prioritize the direction with the larger distance
if (Math.abs(dx) >= Math.abs(dy) && dx !== 0) {
// Horizontal movement has priority
if (dx > 0 && game.snakeGame.direction !== 'left') {
game.snakeGame.nextDirection = 'right';
} else if (dx < 0 && game.snakeGame.direction !== 'right') {
game.snakeGame.nextDirection = 'left';
}
} else if (dy !== 0) {
// Vertical movement
if (dy > 0 && game.snakeGame.direction !== 'up') {
game.snakeGame.nextDirection = 'down';
} else if (dy < 0 && game.snakeGame.direction !== 'down') {
game.snakeGame.nextDirection = 'up';
}
}
}
}
}
};
game.up = function (x, y, obj) {};
game.move = function (x, y, obj) {
if (gameStarted) {
if (currentLevel === 2 && character) {
// In lvl2, character stays in middle horizontal position for Flappy Bird
character.x = 1024;
// Y position is handled by gravity and flapping in update
} else if (currentLevel === 3 && bubbleShooter) {
// Level 3: Update aiming
if (bubbleShooter.cannon) {
var cannonX = bubbleShooter.cannon.x;
var cannonY = bubbleShooter.cannon.y;
aimAngle = Math.atan2(y - cannonY, x - cannonX);
// Limit aiming angle to upward directions only (allow wider range for better aiming)
if (aimAngle > -Math.PI * 0.9 && aimAngle < -Math.PI * 0.1) {
bubbleShooter.cannon.rotation = aimAngle + Math.PI / 2;
// Move current bubble to follow cannon aiming
if (bubbleShooter.currentBubble && bubbleShooter.canShoot) {
var aimDistance = 80; // Distance from cannon tip
bubbleShooter.currentBubble.x = cannonX + Math.cos(aimAngle) * aimDistance;
bubbleShooter.currentBubble.y = cannonY + Math.sin(aimAngle) * aimDistance;
}
}
}
} else if (character) {
// Original behavior for lvl1
var targetX = Math.max(60, Math.min(1988, x));
var targetY = Math.max(60, Math.min(2672, y));
character.x = targetX;
character.y = targetY;
}
}
};
// Start spawning spheres for character selection
// Main game update loop
game.update = function () {
// Handle different game states
if (gameState === 'menu') {
// Stop any playing music in menu
LK.stopMusic();
return; // No updates needed in menu
}
if (gameState === 'levelSelection') {
// Static level boxes, no updates needed
return;
}
if (gameState === 'characterSelection') {
// Static spheres, no updates needed
return;
}
if (gameState === 'countdown') {
return; // Countdown handles itself
}
if (gameState !== 'playing') {
return;
}
// Update sphere counter display
sphereCountText.setText('Esferas: ' + totalSpheres);
// Update timer
if (timerActive && LK.ticks % 60 === 0) {
// Update every second (60 ticks = 1 second at 60 FPS)
gameTimer--;
timerText.setText('Tiempo: ' + gameTimer);
// When timer reaches 0, increase speed
if (gameTimer <= 0 && !speedIncreased) {
speedIncreased = true;
timerActive = false;
// Speed increase will be handled in the speed calculation section below
}
}
// Game playing state - spawn spheres or spikes based on level
spawnTimer++;
if (currentLevel === 2) {
// Level 2: Flappy Bird mechanics - completely separate from other levels
// Spawn pipes
var pipeSpawnRate = 240; // Spawn pipe every 4 seconds (increased from 2 seconds for more spacing)
if (spawnTimer >= pipeSpawnRate) {
var gapY = Math.random() * (2000 - 700) + 700; // Random gap position between y 700-2000
// Create top pipe
var topPipe = new Pipe(true, gapY);
topPipe.x = 2100;
topPipe.y = gapY - pipeGap / 2;
topPipe.lastX = topPipe.x;
pipes.push(topPipe);
game.addChild(topPipe);
// Create bottom pipe
var bottomPipe = new Pipe(false, gapY);
bottomPipe.x = 2100;
bottomPipe.y = gapY + pipeGap / 2;
bottomPipe.lastX = bottomPipe.x;
pipes.push(bottomPipe);
game.addChild(bottomPipe);
spawnTimer = 0;
}
// Handle Flappy Bird physics
if (character) {
// Apply gravity
birdVelocity += gravity;
character.y += birdVelocity;
// Check boundaries - only bounce off top, die on bottom and left
if (character.y < 50) {
character.y = 50;
birdVelocity = 0;
}
// No win condition for minigame 2 - play until you lose
// Check death conditions: left edge or bottom
if (character.x <= 0 || character.y > 2680) {
// Hit left edge or ground - game over
attemptCount++;
storage.attemptCount = attemptCount;
// Track level-specific attempts
if (!levelAttempts[currentLevel]) {
levelAttempts[currentLevel] = 0;
}
levelAttempts[currentLevel]++;
storage.levelAttempts = levelAttempts;
// Convert distance to spheres (divide by 10)
var spheresEarned = Math.floor(distanceTraveled / 10);
totalSpheres += spheresEarned;
storage.totalSpheres = totalSpheres;
LK.showGameOver();
return;
}
// Update distance
distanceTraveled += 3; // Distance increases as pipes move (matches pipe speed)
heightText.setText('Distancia: ' + Math.floor(distanceTraveled) + 'm');
}
// Update pipes and check collisions
for (var p = pipes.length - 1; p >= 0; p--) {
var pipe = pipes[p];
if (pipe.lastX === undefined) pipe.lastX = pipe.x;
var shouldRemovePipe = false;
// Check if off screen
if (pipe.lastX >= -pipeWidth && pipe.x < -pipeWidth) {
shouldRemovePipe = true;
}
// Check collision with character - bounce instead of game over
if (character && pipe.intersects(character)) {
// Bounce character away from pipe
if (pipe.isTop) {
// Hit top pipe - bounce down
character.y = pipe.y + pipe.height + 20;
birdVelocity = 5; // Push down
} else {
// Hit bottom pipe - bounce up
character.y = pipe.y - 20;
birdVelocity = -5; // Push up
}
// Flash character red to indicate collision
LK.effects.flashObject(character, 0xFF0000, 300);
}
// Check if passed pipe for scoring (only check bottom pipes to avoid double scoring)
if (!pipe.isTop && !pipe.scored && pipe.lastX >= character.x && pipe.x < character.x) {
pipe.scored = true;
scoreCounter++;
LK.setScore(scoreCounter);
scoreText.setText('Puntos: ' + LK.getScore());
LK.getSound('collect').play();
}
if (shouldRemovePipe) {
pipe.destroy();
pipes.splice(p, 1);
} else {
pipe.lastX = pipe.x;
}
}
// Level 2 specific updates only
return; // Skip level 1 logic for level 2
} else if (currentLevel === 3) {
// Level 3: Bubble Shooter logic
if (bubbleShooter) {
// Add new rows periodically
if (LK.ticks % 1800 === 0) {
// Every 30 seconds
bubbleShooter.addNewRowFromTop();
}
// No win condition - game continues indefinitely
// Bubble shooter physics will be handled in the class update method
}
return; // Skip other level logic
} else if (currentLevel === 4) {
// Level 4: Snake Game logic
if (game.snakeGame) {
// Snake game handles its own updates
// No additional spawning or logic needed
}
return; // Skip other level logic
} else if (currentLevel === 1) {
// Level 1 logic - completely separate from other levels
var spawnRate = Math.max(30, 90 - difficultyLevel * 5);
if (spawnTimer >= spawnRate) {
spawnMemory();
spawnTimer = 0;
}
}
// Increase difficulty over time
if (LK.getScore() > 0 && LK.getScore() % 10 === 0) {
difficultyLevel = Math.floor(LK.getScore() / 10) + 1;
}
// Spawn rate already handled at top of update function
// Only update memories for level 1
if (currentLevel === 1) {
// Update golden memories
for (var i = goldenMemories.length - 1; i >= 0; i--) {
var golden = goldenMemories[i];
// Initialize lastY if needed
if (golden.lastY === undefined) golden.lastY = golden.y;
var shouldRemove = false;
// Check if off screen
if (golden.lastY <= 2732 && golden.y > 2732) {
shouldRemove = true;
} else if (character && gameStarted && golden.intersects(character)) {
// Check collision with character for game phase
var spherePoints = Math.floor(3 * sphereMultiplier);
// Disgust character gets 2x spheres
if (selectedEmotion === 'disgust') {
spherePoints = Math.floor(6 * sphereMultiplier);
}
LK.setScore(LK.getScore() + spherePoints);
scoreText.setText('Puntos: ' + LK.getScore());
LK.getSound('collect').play();
// Flash character gold
LK.effects.flashObject(character, 0xFFD700, 300);
shouldRemove = true;
} else if (!gameStarted && !countdownActive && golden.characterType && golden.y > 500 && golden.y < 2000) {
// Check collision for character selection phase
handleSphereSelection(golden);
shouldRemove = true;
}
if (shouldRemove) {
golden.destroy();
goldenMemories.splice(i, 1);
} else {
golden.lastY = golden.y;
}
}
// Update corrupted memories
for (var j = corruptedMemories.length - 1; j >= 0; j--) {
var corrupted = corruptedMemories[j];
// Initialize lastY if needed
if (corrupted.lastY === undefined) corrupted.lastY = corrupted.y;
var shouldRemove = false;
// Check if off screen
if (corrupted.lastY <= 2732 && corrupted.y > 2732) {
shouldRemove = true;
} else if (character && corrupted.intersects(character)) {
// Check collision with character
var damage = speedIncreased ? 25 : 20;
health -= damage;
healthText.setText('Vida: ' + health + '/' + maxHealth);
LK.getSound('damage').play();
// Flash character red
LK.effects.flashObject(character, 0xFF0000, 500);
shouldRemove = true;
// Check game over
if (health <= 0) {
// Stop character from following cursor
if (character) {
tween.stop(character);
}
// Increment attempt counter
attemptCount++;
storage.attemptCount = attemptCount;
// Track level-specific attempts
if (!levelAttempts[currentLevel]) {
levelAttempts[currentLevel] = 0;
}
levelAttempts[currentLevel]++;
storage.levelAttempts = levelAttempts;
// Add accumulated points to total spheres
totalSpheres += LK.getScore();
storage.totalSpheres = totalSpheres;
// Show game over immediately
LK.showGameOver();
return;
}
}
if (shouldRemove) {
corrupted.destroy();
corruptedMemories.splice(j, 1);
} else {
corrupted.lastY = corrupted.y;
}
}
}
// Calculate speed only for level 1
if (currentLevel === 1) {
var goldenBaseSpeed = (3 + difficultyLevel * 0.5) * speedMultiplier;
var corruptedBaseSpeed = (4 + difficultyLevel * 0.5) * speedMultiplier;
if (speedIncreased) {
goldenBaseSpeed += goldenBaseSpeed * 0.5;
corruptedBaseSpeed += corruptedBaseSpeed * 0.5;
}
// Double speed for lvl1
goldenBaseSpeed *= 2;
corruptedBaseSpeed *= 2;
// Apply speed boost multiplier if active
if (speedBoostActive) {
goldenBaseSpeed *= 2;
corruptedBaseSpeed *= 2;
}
// Apply calculated speeds
for (var k = 0; k < goldenMemories.length; k++) {
goldenMemories[k].speed = goldenBaseSpeed;
}
for (var l = 0; l < corruptedMemories.length; l++) {
corruptedMemories[l].speed = corruptedBaseSpeed;
}
}
// Win condition removed - game continues indefinitely
// Win condition removed - game continues indefinitely
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Bubble = Container.expand(function (color) {
var self = Container.call(this);
// Color mapping - only 5 colors available, plus rainbow
var colorAssets = {
0: 'bubble_red',
1: 'bubble_blue',
2: 'bubble_green',
3: 'bubble_yellow',
4: 'bubble_purple',
5: 'bubble_red' // Start rainbow with red texture
};
self.colorType = color;
var bubbleGraphics = self.attachAsset(colorAssets[color], {
anchorX: 0.5,
anchorY: 0.5
});
// Grid position
self.gridRow = -1;
self.gridCol = -1;
self.isConnectedToTop = false;
self.visited = false;
self.isRainbow = color === 5;
// Movement properties for shooting
self.velocityX = 0;
self.velocityY = 0;
// Rainbow texture cycling
self.rainbowTimer = 0;
self.currentTextureIndex = 0;
self.rainbowTextures = ['bubble_red', 'bubble_yellow', 'bubble_green', 'bubble_blue', 'bubble_purple'];
self.update = function () {
if (self.isRainbow) {
self.rainbowTimer += 0.1;
// Change texture every 0.5 seconds (30 ticks at 60fps)
if (LK.ticks % 30 === 0) {
self.currentTextureIndex = (self.currentTextureIndex + 1) % self.rainbowTextures.length;
// Remove current texture and add new one
self.removeChild(bubbleGraphics);
bubbleGraphics = self.attachAsset(self.rainbowTextures[self.currentTextureIndex], {
anchorX: 0.5,
anchorY: 0.5
});
}
}
};
return self;
});
var BubbleShooter = Container.expand(function () {
var self = Container.call(this);
// Game grid - hexagonal arrangement
self.bubbleGrid = [];
self.gridRows = 12;
self.gridCols = 16; // Increase columns to cover full 2048px width
self.bubbleRadius = 64; // Adjust radius for proper spacing across 2048px
self.startY = 20; // Start bubbles higher up
// Shooting mechanics
self.cannon = null;
self.currentBubble = null;
self.nextBubble = null;
self.aimLine = [];
self.shootingBubble = null;
self.canShoot = true; // Flag to control when shooting is allowed
// Pressure system
self.missedShots = 0;
self.maxMissedShots = 5;
// Pressure timer system for automatic grid descent
self.pressureTimer = 0;
self.pressureInterval = 300; // Every 5 seconds at 60 FPS (adjustable)
// Initialize grid
self.initGrid = function () {
self.bubbleGrid = []; // Reset grid completely
for (var row = 0; row < self.gridRows; row++) {
self.bubbleGrid[row] = [];
for (var col = 0; col < self.gridCols; col++) {
self.bubbleGrid[row][col] = null;
}
}
// Create danger line
self.dangerLine = self.attachAsset('danger_line', {
anchorX: 0.5,
anchorY: 0.5
});
self.dangerLine.x = 1024;
self.dangerLine.y = 1800; // Position lower than middle
// Create invisible bottom boundary line for bubble expansion
self.bottomBoundary = LK.getAsset('danger_line', {
anchorX: 0.5,
anchorY: 0.5
});
self.bottomBoundary.x = 1024;
self.bottomBoundary.y = 2600; // Near bottom of screen
self.bottomBoundary.alpha = 0; // Make it invisible
self.addChild(self.bottomBoundary);
};
// Get hexagonal position - support infinite rows
self.getHexPosition = function (row, col) {
var radius = self.bubbleRadius;
var diameter = radius * 2;
var rowHeight = radius * 1.732; // Factor de compresión vertical (sqrt(3))
// 1. CÁLCULO DE CENTRADO (Para colocar la grilla completa en el centro de 2048px)
// Se calcula el ancho total teórico de la grilla más el radio final.
var totalGridWidth = self.gridCols * diameter + radius;
var centerOffset = (2048 - totalGridWidth) / 2;
// 2. DESPLAZAMIENTO HEXAGONAL (Mueve las filas impares medio diámetro)
var offsetX = row % 2 * radius;
// POSICIÓN X: (Offset de Centrado) + (Posición por columna) + (Offset hexagonal)
var x = centerOffset + col * diameter + offsetX;
// POSICIÓN Y: (Inicio del Nivel) + (Posición por fila)
var y = self.startY + row * rowHeight;
return {
x: x,
y: y
};
};
// Create initial bubble layout
self.createInitialBubbles = function () {
// Only create bubbles in first 6 rows to leave space for gameplay
var initialRows = Math.min(6, self.gridRows);
for (var row = 0; row < initialRows; row++) {
// The hexagonal grid has N and N-1 columns pattern (16, 15, 16, 15...)
var colsInRow = self.gridCols - row % 2; // 16 or 15 columns
for (var col = 0; col < colsInRow; col++) {
if (Math.random() < 0.8) {
// 80% probability for balanced coverage
// Only normal colors for ceiling bubbles - no rainbow bubbles
var color = Math.floor(Math.random() * 5); // 5 normal colors available
var bubble = new Bubble(color);
var pos = self.getHexPosition(row, col);
bubble.x = pos.x;
bubble.y = pos.y;
bubble.gridRow = row;
bubble.gridCol = col;
self.bubbleGrid[row][col] = bubble;
self.addChild(bubble);
}
}
}
};
// Get neighbors in hexagonal grid
self.getNeighbors = function (row, col) {
var neighbors = [];
var isOddRow = row % 2 === 1;
// Define neighbor offsets for hex grid
var offsets = isOddRow ? [[-1, 0], [-1, 1], [0, -1], [0, 1], [1, 0], [1, 1]] : [[-1, -1], [-1, 0], [0, -1], [0, 1], [1, -1], [1, 0]];
for (var i = 0; i < offsets.length; i++) {
var newRow = row + offsets[i][0];
var newCol = col + offsets[i][1];
if (newRow >= 0 && newRow < self.bubbleGrid.length && newCol >= 0 && newCol < self.gridCols) {
// Ensure the row exists before checking
if (!self.bubbleGrid[newRow]) {
self.bubbleGrid[newRow] = new Array(self.gridCols).fill(null);
}
neighbors.push({
row: newRow,
col: newCol
});
}
}
return neighbors;
};
// Find connected bubbles of same color
self.findConnectedBubbles = function (startRow, startCol, color) {
var connected = [];
var visited = [];
// Initialize visited array to match current grid size
for (var r = 0; r < self.bubbleGrid.length; r++) {
visited[r] = [];
for (var c = 0; c < self.gridCols; c++) {
visited[r][c] = false;
}
}
// Depth-first search
function dfs(row, col) {
if (row < 0 || row >= self.bubbleGrid.length || col < 0 || col >= self.gridCols || !visited[row] || visited[row][col]) {
return;
}
// Check if row exists and has bubbles
if (!self.bubbleGrid[row] || !self.bubbleGrid[row][col] || self.bubbleGrid[row][col].colorType !== color) {
return;
}
visited[row][col] = true;
connected.push({
row: row,
col: col
});
var neighbors = self.getNeighbors(row, col);
for (var i = 0; i < neighbors.length; i++) {
dfs(neighbors[i].row, neighbors[i].col);
}
}
dfs(startRow, startCol);
return connected;
};
// Check which bubbles are connected to top
self.markConnectedToTop = function () {
// Reset all connection flags
for (var row = 0; row < self.bubbleGrid.length; row++) {
if (self.bubbleGrid[row]) {
for (var col = 0; col < self.gridCols; col++) {
if (self.bubbleGrid[row][col]) {
self.bubbleGrid[row][col].isConnectedToTop = false;
self.bubbleGrid[row][col].visited = false;
}
}
}
}
// Mark bubbles connected to top row
function markConnected(row, col) {
if (row < 0 || row >= self.bubbleGrid.length || col < 0 || col >= self.gridCols) {
return;
}
// Ensure row exists
if (!self.bubbleGrid[row]) {
self.bubbleGrid[row] = new Array(self.gridCols).fill(null);
}
if (!self.bubbleGrid[row][col] || self.bubbleGrid[row][col].visited) {
return;
}
self.bubbleGrid[row][col].isConnectedToTop = true;
self.bubbleGrid[row][col].visited = true;
var neighbors = self.getNeighbors(row, col);
for (var i = 0; i < neighbors.length; i++) {
markConnected(neighbors[i].row, neighbors[i].col);
}
}
// Start from top row
for (var col = 0; col < self.gridCols; col++) {
if (self.bubbleGrid[0][col]) {
markConnected(0, col);
}
}
};
// Remove floating bubbles
self.removeFloatingBubbles = function () {
var removed = [];
for (var row = 0; row < self.bubbleGrid.length; row++) {
if (!self.bubbleGrid[row]) continue;
for (var col = 0; col < self.gridCols; col++) {
var bubble = self.bubbleGrid[row][col];
if (bubble && !bubble.isConnectedToTop) {
removed.push(bubble);
self.bubbleGrid[row][col] = null;
// Animate falling
tween(bubble, {
y: 2800,
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish(bubbleRef) {
return function () {
if (bubbleRef && bubbleRef.destroy) {
bubbleRef.destroy();
}
};
}(bubble)
});
}
}
}
return removed.length;
};
// Process bubble elimination
self.processBubbleElimination = function (row, col) {
var bubble = self.bubbleGrid[row][col];
if (!bubble) return;
// Special rainbow bubble behavior
if (bubble.isRainbow) {
// Rainbow bubble makes ALL touching bubbles fall (regardless of color)
var neighbors = self.getNeighbors(row, col);
var bubblesAffected = [];
// Add the rainbow bubble itself
bubblesAffected.push({
row: row,
col: col
});
// Add all neighboring bubbles
for (var n = 0; n < neighbors.length; n++) {
var neighborRow = neighbors[n].row;
var neighborCol = neighbors[n].col;
if (self.bubbleGrid[neighborRow] && self.bubbleGrid[neighborRow][neighborCol]) {
bubblesAffected.push({
row: neighborRow,
col: neighborCol
});
}
}
// Remove all affected bubbles
var redBubblesRemoved = 0;
for (var i = 0; i < bubblesAffected.length; i++) {
var pos = bubblesAffected[i];
var bubbleToRemove = self.bubbleGrid[pos.row][pos.col];
if (bubbleToRemove) {
// Count red bubbles (colorType 0 is red)
if (bubbleToRemove.colorType === 0) {
redBubblesRemoved++;
}
self.bubbleGrid[pos.row][pos.col] = null;
LK.effects.flashObject(bubbleToRemove, 0xffffff, 300);
bubbleToRemove.destroy();
}
}
// Update score for rainbow elimination
LK.setScore(LK.getScore() + bubblesAffected.length * 20);
LK.getSound('collect').play();
// Check for floating bubbles
self.markConnectedToTop();
var floatingCount = self.removeFloatingBubbles();
if (floatingCount > 0) {
LK.setScore(LK.getScore() + floatingCount * 5);
}
self.missedShots = 0;
return true;
}
var connected = self.findConnectedBubbles(row, col, bubble.colorType);
if (connected.length >= 3) {
// Remove connected bubbles
for (var i = 0; i < connected.length; i++) {
var pos = connected[i];
var bubbleToRemove = self.bubbleGrid[pos.row][pos.col];
if (bubbleToRemove) {
self.bubbleGrid[pos.row][pos.col] = null;
LK.effects.flashObject(bubbleToRemove, 0xffffff, 300);
bubbleToRemove.destroy();
}
}
// Update score
LK.setScore(LK.getScore() + connected.length * 10);
LK.getSound('collect').play();
// Check for floating bubbles
self.markConnectedToTop();
var floatingCount = self.removeFloatingBubbles();
if (floatingCount > 0) {
LK.setScore(LK.getScore() + floatingCount * 5);
}
self.missedShots = 0; // Reset pressure counter on successful match
return true;
}
return false;
};
// Move grid down (pressure system) - allow infinite expansion
self.moveGridDown = function () {
// Expand grid if needed
var maxUsedRow = -1;
for (var row = 0; row < self.bubbleGrid.length; row++) {
for (var col = 0; col < self.gridCols; col++) {
if (self.bubbleGrid[row] && self.bubbleGrid[row][col]) {
maxUsedRow = Math.max(maxUsedRow, row);
}
}
}
// Add more rows if bubbles are near the bottom
if (maxUsedRow >= self.bubbleGrid.length - 3) {
for (var i = 0; i < 5; i++) {
// Add 5 more rows
self.bubbleGrid.push(new Array(self.gridCols).fill(null));
}
}
// Move all bubbles down one row
for (var row = self.bubbleGrid.length - 1; row >= 0; row--) {
for (var col = 0; col < self.gridCols; col++) {
if (self.bubbleGrid[row] && self.bubbleGrid[row][col] && row < self.bubbleGrid.length - 1) {
// Ensure next row exists
if (!self.bubbleGrid[row + 1]) {
self.bubbleGrid[row + 1] = new Array(self.gridCols).fill(null);
}
// Move bubble to next row
var bubble = self.bubbleGrid[row][col];
self.bubbleGrid[row + 1][col] = bubble;
self.bubbleGrid[row][col] = null;
bubble.gridRow = row + 1;
var newPos = self.getHexPosition(row + 1, col);
tween(bubble, {
x: newPos.x,
y: newPos.y
}, {
duration: 300
});
}
}
}
};
// Create new shooting bubble
self.createNewBubble = function () {
if (!self.currentBubble) {
var color;
if (Math.random() < 0.1) {
// 10% chance for rainbow bubble
color = 5;
} else {
// 90% chance for normal colors
color = Math.floor(Math.random() * 5);
}
self.currentBubble = new Bubble(color);
// Position bubble properly within BubbleShooter container
self.currentBubble.x = self.cannon ? self.cannon.x : 1024;
self.currentBubble.y = self.cannon ? self.cannon.y - 60 : 2500;
self.addChild(self.currentBubble);
}
if (!self.nextBubble) {
var nextColor;
if (Math.random() < 0.1) {
// 10% chance for rainbow bubble
nextColor = 5;
} else {
// 90% chance for normal colors
nextColor = Math.floor(Math.random() * 5);
}
self.nextBubble = new Bubble(nextColor);
// Position next bubble properly within BubbleShooter container
self.nextBubble.x = self.cannon ? self.cannon.x + 100 : 1124;
self.nextBubble.y = self.cannon ? self.cannon.y - 60 : 2500;
self.nextBubble.alpha = 0.7;
self.addChild(self.nextBubble);
}
};
// Check win condition - always return false to prevent winning
self.checkWin = function () {
return false;
};
// Add new row from top
self.addNewRowFromTop = function () {
// Shift all existing bubbles down one row
for (var row = self.gridRows - 1; row > 0; row--) {
for (var col = 0; col < self.gridCols; col++) {
if (self.bubbleGrid[row - 1][col]) {
var bubble = self.bubbleGrid[row - 1][col];
self.bubbleGrid[row][col] = bubble;
self.bubbleGrid[row - 1][col] = null;
bubble.gridRow = row;
var newPos = self.getHexPosition(row, col);
tween(bubble, {
x: newPos.x,
y: newPos.y
}, {
duration: 300
});
}
}
}
// Add new bubbles to top row
for (var col = 0; col < self.gridCols; col++) {
if (Math.random() < 0.85) {
// 85% chance to place bubble for fuller coverage
// Only normal colors for ceiling bubbles - no rainbow bubbles
var color = Math.floor(Math.random() * 5); // 5 colors available
var bubble = new Bubble(color);
var pos = self.getHexPosition(0, col);
bubble.x = pos.x;
bubble.y = pos.y - 100; // Start above screen
bubble.gridRow = 0;
bubble.gridCol = col;
self.bubbleGrid[0][col] = bubble;
self.addChild(bubble);
// Animate drop into position
tween(bubble, {
y: pos.y
}, {
duration: 500,
easing: tween.bounceOut
});
}
}
};
// Shoot bubble with physics
self.shootBubble = function (angle) {
if (self.currentBubble && self.canShoot) {
var bullet = self.currentBubble;
// Use shootPower from global scope (defined as 15)
var power = 15;
// Calculate velocity components
bullet.velocityX = Math.cos(angle) * power;
bullet.velocityY = Math.sin(angle) * power;
// Set bullet to cannon position - ensure proper positioning within container
bullet.x = self.cannon ? self.cannon.x : 1024;
bullet.y = self.cannon ? self.cannon.y : 2600;
// Make it the shooting bubble
self.shootingBubble = bullet;
// Disable shooting until collision
self.canShoot = false;
// Prepare next bubble
self.currentBubble = self.nextBubble;
if (self.currentBubble) {
self.currentBubble.x = self.cannon ? self.cannon.x : 1024;
self.currentBubble.y = self.cannon ? self.cannon.y - 60 : 2500;
self.currentBubble.alpha = 1;
}
var nextColor;
if (Math.random() < 0.1) {
// 10% chance for rainbow bubble
nextColor = 5;
} else {
// 90% chance for normal colors
nextColor = Math.floor(Math.random() * 5);
}
self.nextBubble = new Bubble(nextColor);
self.nextBubble.x = self.cannon ? self.cannon.x + 100 : 1124;
self.nextBubble.y = self.cannon ? self.cannon.y - 60 : 2500;
self.nextBubble.alpha = 0.7;
self.addChild(self.nextBubble);
}
};
// Check collision between shooting bubble and grid - optimized collision detection
self.checkCollision = function (shootingBubble) {
// Only check rows that the shooting bubble could actually hit
var minRow = Math.max(0, Math.floor((shootingBubble.y - self.bubbleRadius * 2) / (self.bubbleRadius * 1.7)));
var maxRow = Math.min(self.bubbleGrid.length - 1, Math.floor((shootingBubble.y + self.bubbleRadius * 2) / (self.bubbleRadius * 1.7)));
for (var row = minRow; row <= maxRow; row++) {
if (self.bubbleGrid[row]) {
for (var col = 0; col < self.gridCols; col++) {
var gridBubble = self.bubbleGrid[row][col];
if (gridBubble) {
var dx = shootingBubble.x - gridBubble.x;
var dy = shootingBubble.y - gridBubble.y;
var distanceSquared = dx * dx + dy * dy;
// Use squared distance to avoid expensive sqrt calculation
// 85^2 = 7225 for better collision detection
if (distanceSquared <= 7225) {
return {
row: row,
col: col,
bubble: gridBubble
};
}
}
}
}
}
return null;
};
// Find closest grid position for bubble attachment - optimized and simplified
self.findClosestGridPosition = function (x, y, collisionBubble) {
var minDist = 999999;
var targetRow = -1;
var targetCol = -1;
// If we have a collision bubble, only check its immediate neighbors
if (collisionBubble) {
var neighbors = self.getNeighbors(collisionBubble.row, collisionBubble.col);
for (var n = 0; n < neighbors.length; n++) {
var nRow = neighbors[n].row;
var nCol = neighbors[n].col;
// Ensure valid bounds
if (nRow >= 0 && nCol >= 0 && nCol < self.gridCols) {
// Ensure row exists
while (self.bubbleGrid.length <= nRow) {
self.bubbleGrid.push(new Array(self.gridCols).fill(null));
}
if (!self.bubbleGrid[nRow]) {
self.bubbleGrid[nRow] = new Array(self.gridCols).fill(null);
}
if (!self.bubbleGrid[nRow][nCol]) {
var pos = self.getHexPosition(nRow, nCol);
var dx = x - pos.x;
var dy = y - pos.y;
var distSquared = dx * dx + dy * dy;
if (distSquared < minDist) {
minDist = distSquared;
targetRow = nRow;
targetCol = nCol;
}
}
}
}
if (targetRow >= 0 && targetCol >= 0) {
return {
row: targetRow,
col: targetCol
};
}
}
// Fallback: find position in first few rows
var searchRow = Math.max(0, Math.min(5, Math.floor((y - self.startY) / (self.bubbleRadius * 1.7))));
// Ensure the row exists
while (self.bubbleGrid.length <= searchRow + 2) {
self.bubbleGrid.push(new Array(self.gridCols).fill(null));
}
// Search a small area around the calculated position
for (var rowOffset = 0; rowOffset < 3; rowOffset++) {
var row = searchRow + rowOffset;
if (row >= 0 && row < self.bubbleGrid.length && self.bubbleGrid[row]) {
for (var col = 0; col < self.gridCols; col++) {
if (!self.bubbleGrid[row][col]) {
return {
row: row,
col: col
};
}
}
}
}
// If no position found, return first available position in first row
for (var col = 0; col < self.gridCols; col++) {
if (!self.bubbleGrid[0][col]) {
return {
row: 0,
col: col
};
}
}
// Last resort: return valid position
return {
row: 0,
col: 0
};
};
// Check lose condition - bubbles touching red danger line
self.checkLose = function () {
// Check if any bubble touches the danger line
for (var row = 0; row < self.bubbleGrid.length; row++) {
if (self.bubbleGrid[row]) {
for (var col = 0; col < self.gridCols; col++) {
var bubble = self.bubbleGrid[row][col];
if (bubble) {
// Check if bubble position is at or below danger line
if (bubble.y + self.bubbleRadius >= self.dangerLine.y - 10) {
return true;
}
}
}
}
}
return false;
};
// Update method to handle shooting physics - optimized
self.update = function () {
// Pressure system - increment timer and check for automatic descent
self.pressureTimer++;
if (self.pressureTimer >= self.pressureInterval) {
self.moveGridDown(); // Move grid down automatically
self.pressureTimer = 0; // Reset timer
// Check lose condition immediately after moving grid down
if (self.checkLose()) {
LK.showGameOver();
return;
}
}
// Check if bubbles are approaching bottom boundary and generate new rows
var maxBubbleY = -1;
for (var row = 0; row < self.bubbleGrid.length; row++) {
if (self.bubbleGrid[row]) {
for (var col = 0; col < self.gridCols; col++) {
if (self.bubbleGrid[row][col]) {
maxBubbleY = Math.max(maxBubbleY, self.bubbleGrid[row][col].y);
}
}
}
}
// If bubbles are getting close to bottom boundary, add new rows from top
if (maxBubbleY > self.bottomBoundary.y - 200) {
self.addNewRowFromTop();
}
// Update shooting bubble physics
if (self.shootingBubble) {
var bullet = self.shootingBubble;
// Move bullet with velocity
bullet.x += bullet.velocityX;
bullet.y += bullet.velocityY;
// Wall bouncing - reverse X velocity when hitting side walls
if (bullet.x <= self.bubbleRadius) {
bullet.x = self.bubbleRadius;
bullet.velocityX = Math.abs(bullet.velocityX); // Bounce right
} else if (bullet.x >= 2048 - self.bubbleRadius) {
bullet.x = 2048 - self.bubbleRadius;
bullet.velocityX = -Math.abs(bullet.velocityX); // Bounce left
}
// Check collision with existing bubbles
var collision = self.checkCollision(bullet);
var hitTop = bullet.y <= self.startY + self.bubbleRadius;
var shouldAttach = collision || hitTop;
if (shouldAttach) {
// Find closest empty grid position
var closestPos = self.findClosestGridPosition(bullet.x, bullet.y, collision);
if (closestPos && closestPos.row >= 0 && closestPos.col >= 0 && closestPos.col < self.gridCols) {
// Ensure row exists
while (self.bubbleGrid.length <= closestPos.row) {
self.bubbleGrid.push(new Array(self.gridCols).fill(null));
}
if (!self.bubbleGrid[closestPos.row]) {
self.bubbleGrid[closestPos.row] = new Array(self.gridCols).fill(null);
}
// Attach bubble to grid
var gridPos = self.getHexPosition(closestPos.row, closestPos.col);
bullet.x = gridPos.x;
bullet.y = gridPos.y;
bullet.gridRow = closestPos.row;
bullet.gridCol = closestPos.col;
// Add to grid
self.bubbleGrid[closestPos.row][closestPos.col] = bullet;
// Process bubble elimination
var eliminated = self.processBubbleElimination(closestPos.row, closestPos.col);
if (!eliminated) {
// No match found - increment missed shots
self.missedShots++;
if (self.missedShots >= self.maxMissedShots) {
self.moveGridDown();
self.missedShots = 0;
}
}
} else {
// No valid position found - remove bullet
bullet.destroy();
self.missedShots++;
if (self.missedShots >= self.maxMissedShots) {
self.moveGridDown();
self.missedShots = 0;
}
}
// Clear shooting bubble and re-enable shooting
self.shootingBubble = null;
self.canShoot = true;
// Create new bubble for next shot
self.createNewBubble();
// Update missed shots display
if (missedShotsText) {
missedShotsText.setText('Fallos: ' + self.missedShots + '/' + self.maxMissedShots);
}
// Check lose condition
if (self.checkLose()) {
LK.showGameOver();
return;
}
}
}
};
return self;
});
// Add spikes around the corrupted memory
var CorruptedMemory = Container.expand(function () {
var self = Container.call(this);
var memoryGraphics = self.attachAsset('corruptedMemory', {
anchorX: 0.5,
anchorY: 0.5
});
// Add spikes around the corrupted memory
var spike1 = self.attachAsset('spike', {
anchorX: 0.5,
anchorY: 0.5
});
spike1.x = -30;
spike1.y = -30;
spike1.rotation = Math.PI / 4;
var spike2 = self.attachAsset('spike', {
anchorX: 0.5,
anchorY: 0.5
});
spike2.x = 30;
spike2.y = -30;
spike2.rotation = -Math.PI / 4;
var spike3 = self.attachAsset('spike', {
anchorX: 0.5,
anchorY: 0.5
});
spike3.x = 30;
spike3.y = 30;
spike3.rotation = Math.PI / 4;
var spike4 = self.attachAsset('spike', {
anchorX: 0.5,
anchorY: 0.5
});
spike4.x = -30;
spike4.y = 30;
spike4.rotation = -Math.PI / 4;
self.speed = 4;
self.lastY = undefined;
self.update = function () {
self.y += self.speed;
};
return self;
});
var EmotionCharacter = Container.expand(function (emotionType) {
var self = Container.call(this);
var characterGraphics = self.attachAsset(emotionType, {
anchorX: 0.5,
anchorY: 0.5
});
self.emotionType = emotionType;
self.health = 100;
self.maxHealth = 100;
return self;
});
var GoldenMemory = Container.expand(function () {
var self = Container.call(this);
var memoryGraphics = self.attachAsset('goldenMemory', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 3;
self.lastY = undefined;
self.update = function () {
self.y += self.speed;
};
return self;
});
var Pipe = Container.expand(function (isTop, gapY) {
var self = Container.call(this);
var pipeGraphics = self.attachAsset('spike', {
anchorX: 0.5,
anchorY: isTop ? 1 : 0
});
pipeGraphics.tint = 0x4CAF50;
pipeGraphics.width = pipeWidth;
pipeGraphics.height = isTop ? gapY - pipeGap / 2 : 2732 - (gapY + pipeGap / 2);
self.speed = 3;
self.isTop = isTop;
self.scored = false;
self.lastX = undefined;
self.update = function () {
self.x -= self.speed;
};
return self;
});
var SideSpike = Container.expand(function (fromLeft) {
var self = Container.call(this);
var spikeGraphics = self.attachAsset('espinas', {
anchorX: 0.5,
anchorY: 0.5
});
self.fromLeft = fromLeft;
self.speed = fromLeft ? 8 : -8;
self.lastX = undefined;
self.update = function () {
self.x += self.speed;
};
return self;
});
var SnakeGame = Container.expand(function () {
var self = Container.call(this);
// Grid configuration
self.gridSize = 40; // Pixels per cell
self.gridWidth = 50; // Cells horizontally
self.gridHeight = 35; // Cells vertically
// Snake state
self.snake = [{
x: 10,
y: 10
}]; // Start position
self.direction = 'right';
self.nextDirection = 'right';
self.moveTimer = 0;
self.moveInterval = 15; // Move every 15 frames
// Food
self.foodPosition = null;
// Visual elements for destroy/recreate pattern
self.gusanoSprites = [];
self.foodSprite = null;
// Convert grid coordinates to pixel coordinates
self.getPixelPosition = function (gridX, gridY) {
var floorLeft = self.floor.x - 1800 / 2;
var floorTop = self.floor.y - 2400 / 2;
return {
x: floorLeft + gridX * self.gridSize + self.gridSize / 2,
y: floorTop + gridY * self.gridSize + self.gridSize / 2
};
};
// Place food at random empty position
self.placeFood = function () {
var validPositions = [];
// Find all empty positions
for (var x = 0; x < self.gridWidth; x++) {
for (var y = 0; y < self.gridHeight; y++) {
var isEmpty = true;
// Check if position is occupied by snake
for (var i = 0; i < self.snake.length; i++) {
if (self.snake[i].x === x && self.snake[i].y === y) {
isEmpty = false;
break;
}
}
if (isEmpty) {
validPositions.push({
x: x,
y: y
});
}
}
}
if (validPositions.length > 0) {
var randomIndex = Math.floor(Math.random() * validPositions.length);
return validPositions[randomIndex];
}
// Fallback if no valid positions
return {
x: 0,
y: 0
};
};
// Initialize game
self.initGame = function () {
// Increase gridSize dramatically for better visibility
self.gridSize = 120;
// Make snake movement slower
self.moveInterval = 15; // Move every 15 frames (increased from default)
// Recalculate grid dimensions based on floor size
var floorWidth = 1800; // Actual playable width
var floorHeight = 2400; // Actual playable height
self.gridWidth = Math.floor(floorWidth / self.gridSize); // ~22 cells wide
self.gridHeight = Math.floor(floorHeight / self.gridSize); // ~30 cells tall
// Create floor background - the actual playable area
self.floor = self.attachAsset('suelo', {
anchorX: 0.5,
anchorY: 0.5
});
self.floor.x = 1024; // Center horizontally
self.floor.y = 1366; // Center vertically
// Scale floor to exact playable dimensions
self.floor.scaleX = floorWidth / 500; // Scale to desired width
self.floor.scaleY = floorHeight / 500; // Scale to desired height
// Ensure floor appears below snake segments
self.floor.zIndex = -1;
// Calculate floor boundaries for proper wall placement
var floorLeft = self.floor.x - floorWidth / 2;
var floorRight = self.floor.x + floorWidth / 2;
var floorTop = self.floor.y - floorHeight / 2;
var floorBottom = self.floor.y + floorHeight / 2;
// Create four walls using pared_de_minijuego_4 - positioned exactly at floor edges
// Top wall
self.topWall = self.attachAsset('pared_de_minijuego_4', {
anchorX: 0.5,
anchorY: 0.5
});
self.topWall.x = 1024;
self.topWall.y = floorTop - 50; // Position above floor
self.topWall.scaleX = (floorWidth + 200) / 73; // Scale to cover full width plus wall thickness
self.topWall.scaleY = 1;
// Bottom wall
self.bottomWall = self.attachAsset('pared_de_minijuego_4', {
anchorX: 0.5,
anchorY: 0.5
});
self.bottomWall.x = 1024;
self.bottomWall.y = floorBottom + 50; // Position below floor
self.bottomWall.scaleX = (floorWidth + 200) / 73; // Scale to cover full width plus wall thickness
self.bottomWall.scaleY = 1;
// Left wall
self.leftWall = self.attachAsset('pared_de_minijuego_4', {
anchorX: 0.5,
anchorY: 0.5
});
self.leftWall.x = floorLeft - 50; // Position left of floor
self.leftWall.y = 1366;
self.leftWall.rotation = Math.PI / 2; // Rotate 90 degrees
self.leftWall.scaleX = (floorHeight + 200) / 73; // Scale to cover full height plus wall thickness
self.leftWall.scaleY = 1;
// Right wall
self.rightWall = self.attachAsset('pared_de_minijuego_4', {
anchorX: 0.5,
anchorY: 0.5
});
self.rightWall.x = floorRight + 50; // Position right of floor
self.rightWall.y = 1366;
self.rightWall.rotation = Math.PI / 2; // Rotate 90 degrees
self.rightWall.scaleX = (floorHeight + 200) / 73; // Scale to cover full height plus wall thickness
self.rightWall.scaleY = 1;
// Place food and create visuals
self.foodPosition = self.placeFood();
self.createVisuals();
};
// Render game - destroys and recreates all visuals to ensure top layer
self.renderGame = function () {
// STEP 1: DESTROY OLD SPRITES
if (self.foodSprite) {
self.foodSprite.destroy();
self.foodSprite = null;
}
for (var i = 0; i < self.gusanoSprites.length; i++) {
self.gusanoSprites[i].destroy();
}
self.gusanoSprites = [];
// STEP 2: DRAW FOOD (drawn first)
if (self.foodPosition) {
var foodPosPix = self.getPixelPosition(self.foodPosition.x, self.foodPosition.y);
self.foodSprite = LK.getAsset('goldenMemory', {
anchorX: 0.5,
anchorY: 0.5
});
self.foodSprite.x = foodPosPix.x;
self.foodSprite.y = foodPosPix.y;
self.foodSprite.width = self.gridSize;
self.foodSprite.height = self.gridSize;
self.foodSprite.tint = 0xFF0000; // Red apple/food
self.addChild(self.foodSprite);
}
// STEP 3: DRAW SNAKE (drawn last, appears on top)
for (var i = 0; i < self.snake.length; i++) {
var segmentPosPix = self.getPixelPosition(self.snake[i].x, self.snake[i].y);
// Use green snake segments
var sprite = LK.getAsset('gusano_minijuego4', {
anchorX: 0.5,
anchorY: 0.5
});
sprite.x = segmentPosPix.x;
sprite.y = segmentPosPix.y;
sprite.width = self.gridSize;
sprite.height = self.gridSize;
sprite.tint = 0x00FF00; // Bright green for snake
self.addChild(sprite);
self.gusanoSprites.push(sprite);
}
};
// Create visual elements
self.createVisuals = function () {
// Use renderGame method for consistent rendering
self.renderGame();
};
// Update visuals
self.updateVisuals = function () {
// Use renderGame method for consistent rendering
self.renderGame();
};
// Check game over conditions
self.checkGameOver = function (headPosition) {
// Check wall collision
if (headPosition.x < 0 || headPosition.x >= self.gridWidth || headPosition.y < 0 || headPosition.y >= self.gridHeight) {
return true;
}
// Check self collision
for (var i = 0; i < self.snake.length; i++) {
if (self.snake[i].x === headPosition.x && self.snake[i].y === headPosition.y) {
return true;
}
}
return false;
};
// Main update loop
self.update = function () {
// Control movement speed
self.moveTimer++;
if (self.moveTimer < self.moveInterval) {
return;
}
self.moveTimer = 0;
// Update direction
self.direction = self.nextDirection;
// Calculate new head position
var head = self.snake[0];
var newHead = {
x: head.x,
y: head.y
};
if (self.direction === 'up') {
newHead.y -= 1;
} else if (self.direction === 'down') {
newHead.y += 1;
} else if (self.direction === 'left') {
newHead.x -= 1;
} else if (self.direction === 'right') {
newHead.x += 1;
}
// Check game over
if (self.checkGameOver(newHead)) {
LK.showGameOver();
return;
}
// Add new head
self.snake.unshift(newHead);
// Check food consumption
if (newHead.x === self.foodPosition.x && newHead.y === self.foodPosition.y) {
// Eat food - snake grows, place new food
LK.setScore(LK.getScore() + 10);
// Place new food (visuals will be handled by renderGame)
self.foodPosition = self.placeFood();
LK.getSound('collect').play();
} else {
// Normal movement - remove tail
self.snake.pop();
}
// Render all visuals (final step - ensures snake appears on top)
self.renderGame();
};
// Keyboard controls are handled by the global keyDown event listener
// No mouse click handling needed for Snake Game
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
var selectedEmotion = storage.selectedEmotion || 'joy';
var gameStarted = false;
var countdownActive = false;
var countdownValue = 3;
var character = null;
var goldenMemories = [];
var corruptedMemories = [];
var spawnTimer = 0;
var difficultyLevel = 1;
var health = 100;
var maxHealth = 100;
// Game state management
var gameState = 'menu'; // 'menu', 'levelSelection', 'characterSelection', 'countdown', 'playing'
var currentLevel = storage.currentLevel || 1;
var levelBoxes = [];
var characterSelectionActive = false;
var selectionButtons = [];
// Timer variables
var gameTimer = 223; // 223 seconds
var timerActive = false;
var speedIncreased = false;
// Character pricing and spheres
var totalSpheres = storage.totalSpheres || 0;
var attemptCount = storage.attemptCount || 0;
// Purchase limits for shop items (max 20 each)
var purchaseCounts = storage.purchaseCounts || {
sphere_multiplier: 0,
speed_multiplier: 0,
health_bonus: 0
};
var characterPrices = {
joy: 0,
// Free
anger: 60,
disgust: 120,
fear: 180,
tristesa: 240
};
var ownedCharacters = storage.ownedCharacters || ['joy']; // Joy is free by default
// Level-specific attempt tracking
var levelAttempts = storage.levelAttempts || {};
// All levels are unlocked by default, no need to track unlocked levels
// Speed boost variables for level 1
var speedBoostActive = false;
var speedBoostTimer = 0;
// Upgrade system variables
var sphereMultiplier = storage.sphereMultiplier || 1;
var speedMultiplier = storage.speedMultiplier || 1;
var maxHealthBonus = storage.maxHealthBonus || 0;
var shopItems = [];
// Shop timer variables
var shopTimer = 10;
var shopTimerActive = false;
var shopTimerText = null;
var shopTimerInterval = null;
// Level 2 specific variables - Flappy Bird style
var sideSpikes = [];
var birdVelocity = 0;
var gravity = 0.8;
var flapStrength = -12;
var pipes = [];
var pipeGap = 400;
var pipeWidth = 100;
var distanceTraveled = 0;
var scoreCounter = 0;
// Level 3 specific variables - Bubble Shooter
var bubbleShooter = null;
var aimAngle = 0;
var aimGuide = [];
var shootPower = 15;
// Missed shots counter for Level 3
var missedShotsText = new Text2('Fallos: 0/5', {
size: 50,
fill: 0xFFFFFF
});
missedShotsText.anchor.set(1, 1);
missedShotsText.alpha = 0; // Hide initially
LK.gui.bottomRight.addChild(missedShotsText);
var heightText = new Text2('Distancia: 0m', {
size: 50,
fill: 0xFFFFFF
});
heightText.anchor.set(0, 0);
LK.gui.bottomLeft.addChild(heightText);
heightText.y = 100;
// Add invisible collision box to heightText
var heightTextBox = LK.getAsset('level1', {
anchorX: 0,
anchorY: 0,
scaleX: 1.5,
scaleY: 0.5
});
heightTextBox.alpha = 0;
heightTextBox.x = 0;
heightTextBox.y = 100;
heightTextBox.textElement = heightText;
LK.gui.bottomLeft.addChild(heightTextBox);
// heightText no longer auto-hides
// UI Elements
var scoreText = new Text2('Puntos: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
scoreText.alpha = 0; // Hide initially
LK.gui.top.addChild(scoreText);
// Add invisible collision box to scoreText
var scoreTextBox = LK.getAsset('level1', {
anchorX: 0.5,
anchorY: 0,
scaleX: 1.5,
scaleY: 0.6
});
scoreTextBox.alpha = 0;
scoreTextBox.x = 0;
scoreTextBox.y = 0;
scoreTextBox.textElement = scoreText;
LK.gui.top.addChild(scoreTextBox);
// scoreText no longer auto-hides
var healthText = new Text2('Vida: 100/100', {
size: 50,
fill: 0xFF0000
});
healthText.anchor.set(1, 0);
LK.gui.topRight.addChild(healthText);
// Add invisible collision box to healthText
var healthTextBox = LK.getAsset('level1', {
anchorX: 1,
anchorY: 0,
scaleX: 1.2,
scaleY: 0.5
});
healthTextBox.alpha = 0;
healthTextBox.x = 0;
healthTextBox.y = 0;
healthTextBox.textElement = healthText;
LK.gui.topRight.addChild(healthTextBox);
// healthText no longer auto-hides
// Timer display
var timerText = new Text2('Tiempo: 223', {
size: 50,
fill: 0xFFFFFF
});
timerText.anchor.set(0, 1);
LK.gui.bottomLeft.addChild(timerText);
// Add invisible collision box to timerText
var timerTextBox = LK.getAsset('level1', {
anchorX: 0,
anchorY: 1,
scaleX: 1.2,
scaleY: 0.5
});
timerTextBox.alpha = 0;
timerTextBox.x = 0;
timerTextBox.y = 0;
timerTextBox.textElement = timerText;
LK.gui.bottomLeft.addChild(timerTextBox);
// timerText no longer auto-hides
// Sphere counter text (moved to where energy bar was)
var sphereCountText = new Text2('Esferas: ' + totalSpheres, {
size: 40,
fill: 0xFFD700
});
sphereCountText.anchor.set(1, 0);
sphereCountText.x = -20;
sphereCountText.y = 80;
LK.gui.topRight.addChild(sphereCountText);
// Add invisible collision box to sphereCountText
var sphereCountTextBox = LK.getAsset('level1', {
anchorX: 1,
anchorY: 0,
scaleX: 1.2,
scaleY: 0.5
});
sphereCountTextBox.alpha = 0;
sphereCountTextBox.x = -20;
sphereCountTextBox.y = 80;
sphereCountTextBox.textElement = sphereCountText;
LK.gui.topRight.addChild(sphereCountTextBox);
// sphereCountText no longer auto-hides
// Menu button - rectangular button to return to level selection (moved to bottom right)
var menuButton = LK.getAsset('level1', {
anchorX: 1,
anchorY: 1,
scaleX: 0.8,
scaleY: 0.6
});
menuButton.x = 2048 - 50;
menuButton.y = 2732 - 50;
menuButton.tint = 0x333333;
menuButton.alpha = 0; // Hide initially
game.addChild(menuButton);
var menuButtonText = new Text2('MENU', {
size: 50,
fill: 0xFFFFFF
});
menuButtonText.anchor.set(0.5, 0.5);
menuButtonText.x = 2048 - 50 - 240 / 2; // Center horizontally within button
menuButtonText.y = 2732 - 50 - 120 / 2; // Center vertically within button
menuButtonText.alpha = 0; // Hide initially
game.addChild(menuButtonText);
// Main Menu UI
var titleText = new Text2('INTENSAMENTE', {
size: 120,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 800;
game.addChild(titleText);
// Add invisible collision box to titleText
var titleTextBox = LK.getAsset('level1', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 0.8
});
titleTextBox.alpha = 0;
titleTextBox.x = titleText.x;
titleTextBox.y = titleText.y;
titleTextBox.textElement = titleText;
game.addChild(titleTextBox);
// titleText no longer auto-hides
var playButton = new Text2('JUGAR', {
size: 80,
fill: 0x00FF00
});
playButton.anchor.set(0.5, 0.5);
playButton.x = 1024;
playButton.y = 1200;
game.addChild(playButton);
// Add invisible collision box to playButton
var playButtonBox = LK.getAsset('level1', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.8
});
playButtonBox.alpha = 0;
playButtonBox.x = playButton.x;
playButtonBox.y = playButton.y;
playButtonBox.textElement = playButton;
game.addChild(playButtonBox);
// playButton no longer auto-hides
var instructionText = new Text2('Selecciona tu personaje', {
size: 80,
fill: 0xFFFFFF
});
instructionText.anchor.set(0.5, 0.5);
instructionText.x = 1024;
instructionText.y = 600;
instructionText.alpha = 0;
instructionText.visible = false;
game.addChild(instructionText);
// Add invisible collision box to instructionText
var instructionTextBox = LK.getAsset('level1', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 0.8
});
instructionTextBox.alpha = 0;
instructionTextBox.x = instructionText.x;
instructionTextBox.y = instructionText.y;
instructionTextBox.textElement = instructionText;
game.addChild(instructionTextBox);
// instructionText no longer auto-hides
instructionText.showWithTimeout = function () {
instructionText.alpha = 1;
instructionText.visible = true;
};
var countdownText = new Text2('3', {
size: 200,
fill: 0xFFFFFF
});
countdownText.anchor.set(0.5, 0.5);
countdownText.x = 1024;
countdownText.y = 1366;
countdownText.alpha = 0;
countdownText.visible = false;
game.addChild(countdownText);
// Add invisible collision box to countdownText
var countdownTextBox = LK.getAsset('level1', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.2
});
countdownTextBox.alpha = 0;
countdownTextBox.x = countdownText.x;
countdownTextBox.y = countdownText.y;
countdownTextBox.textElement = countdownText;
game.addChild(countdownTextBox);
// countdownText no longer auto-hides
countdownText.showWithTimeout = function () {
countdownText.alpha = 1;
countdownText.visible = true;
};
// Spawn static character selection spheres in center
function spawnCharacterSelectionSpheres() {
// Only spawn once
if (goldenMemories.length === 0) {
var emotions = [{
type: 'joy',
name: 'ALEGRÍA',
color: 0xffeb3b,
x: 524,
y: 800
}, {
type: 'anger',
name: 'ENOJO',
color: 0xf44336,
x: 1524,
y: 800
}, {
type: 'disgust',
name: 'DESAGRADO',
color: 0x4caf50,
x: 524,
y: 1200
}, {
type: 'fear',
name: 'MIEDO',
color: 0x9c27b0,
x: 1024,
y: 1200
}, {
type: 'tristesa',
name: 'TRISTEZA',
color: 0x2196f3,
x: 1524,
y: 1200
}];
// Hide score text in character selection
scoreText.alpha = 0;
// Add spheres counter display only if not lvl1
if (currentLevel !== 1) {
var spheresText = new Text2('Esferas: ' + totalSpheres, {
size: 60,
fill: 0xFFD700
});
spheresText.anchor.set(0.5, 0.5);
spheresText.x = 1024;
spheresText.y = 500;
game.addChild(spheresText);
}
for (var i = 0; i < emotions.length; i++) {
var emotionData = emotions[i];
// Create character image instead of sphere
var characterImage = LK.getAsset(emotionData.type, {
anchorX: 0.5,
anchorY: 0.5
});
characterImage.x = emotionData.x;
characterImage.y = emotionData.y;
characterImage.characterType = emotionData.type;
game.addChild(characterImage);
goldenMemories.push(characterImage);
// Check if character is owned or affordable
var isOwned = ownedCharacters.indexOf(emotionData.type) !== -1;
var canAfford = totalSpheres >= characterPrices[emotionData.type];
var price = characterPrices[emotionData.type];
// Dim character if not owned and can't afford
if (!isOwned && !canAfford) {
characterImage.alpha = 0.3;
}
// Add text label with price
var labelText = emotionData.name;
if (!isOwned) {
labelText += '\nPrecio: ' + price + ' esferas';
} else {
labelText += '\nDISPONIBLE';
}
var nameText = new Text2(labelText, {
size: 35,
fill: isOwned ? 0x00FF00 : canAfford ? 0xFFFFFF : 0xFF6666
});
nameText.anchor.set(0.5, 0.5);
nameText.x = emotionData.x;
nameText.y = emotionData.y + 150;
game.addChild(nameText);
characterImage.nameText = nameText;
characterImage.isOwned = isOwned;
characterImage.canAfford = canAfford;
}
}
}
// Handle sphere selection for character choice
function handleSphereSelection(sphere) {
if (sphere.characterType) {
var characterType = sphere.characterType;
var isOwned = ownedCharacters.indexOf(characterType) !== -1;
var canAfford = totalSpheres >= characterPrices[characterType];
var price = characterPrices[characterType];
// Check if character can be selected
if (!isOwned && !canAfford) {
// Flash red to indicate can't select
LK.effects.flashObject(sphere, 0xFF0000, 500);
// Play error sound
LK.getSound('error').play();
// Show insufficient spheres message
var errorText = new Text2('No tienes esferas suficientes', {
size: 60,
fill: 0xFF0000
});
errorText.anchor.set(0.5, 0.5);
errorText.x = 1024;
errorText.y = 2200;
game.addChild(errorText);
// Remove error message after 2 seconds
LK.setTimeout(function () {
errorText.destroy();
}, 2000);
return;
}
// If not owned but can afford, purchase the character
if (!isOwned && canAfford) {
totalSpheres -= price;
ownedCharacters.push(characterType);
storage.totalSpheres = totalSpheres;
storage.ownedCharacters = ownedCharacters;
// Update spheres display after purchase
var spheresTextElements = [];
for (var k = 0; k < game.children.length; k++) {
if (game.children[k].text && game.children[k].text.indexOf('Esferas:') === 0) {
game.children[k].setText('Esferas: ' + totalSpheres);
break;
}
}
} else if (isOwned) {
// Character is already owned, no need to purchase again
} else {
// Character not owned and can't afford - deduct price anyway and add to owned
totalSpheres -= price;
if (totalSpheres < 0) totalSpheres = 0; // Prevent negative spheres
ownedCharacters.push(characterType);
storage.totalSpheres = totalSpheres;
storage.ownedCharacters = ownedCharacters;
// Update spheres display after purchase
for (var k = 0; k < game.children.length; k++) {
if (game.children[k].text && game.children[k].text.indexOf('Esferas:') === 0) {
game.children[k].setText('Esferas: ' + totalSpheres);
break;
}
}
}
selectedEmotion = characterType;
storage.selectedEmotion = selectedEmotion;
// Flash selected sphere
LK.effects.flashObject(sphere, 0xFFFFFF, 500);
// Clear all spheres and their labels
for (var i = goldenMemories.length - 1; i >= 0; i--) {
if (goldenMemories[i].nameText) {
goldenMemories[i].nameText.destroy();
}
goldenMemories[i].destroy();
goldenMemories.splice(i, 1);
}
// Instruction text already removed, no hiding needed
// Go to shop before starting game
LK.setTimeout(function () {
createShop();
}, 600);
}
}
// Create level selection UI
function createLevelSelection() {
gameState = 'levelSelection';
// Hide menu elements
titleText.alpha = 0;
playButton.alpha = 0;
// Level selection title removed to avoid interference
// Create level boxes in a grid (2 columns, 5 rows)
var levelNames = ['MINIJUEGO 1', 'MINIJUEGO 2', 'MINIJUEGO 3', 'MINIJUEGO 4', 'MINIJUEGO 5', 'MINIJUEGO 6', 'MINIJUEGO 7', 'MINIJUEGO 8', 'MINIJUEGO 9', 'MINIJUEGO 10'];
var levelAssets = ['level1', 'level2', 'level3', 'level4', 'level5', 'level6', 'level7', 'level8', 'level9', 'level10'];
for (var i = 0; i < 10; i++) {
var col = i % 2;
var row = Math.floor(i / 2);
var levelBox = LK.getAsset(levelAssets[i], {
anchorX: 0.5,
anchorY: 0.5
});
levelBox.x = 600 + col * 848; // 600 and 1448 for two columns
levelBox.y = 700 + row * 280; // Spacing between rows
levelBox.levelNumber = i + 1;
levelBox.levelName = levelNames[i];
game.addChild(levelBox);
levelBoxes.push(levelBox);
// All levels are unlocked
var isUnlocked = true;
// No dimming since all levels are unlocked
// Add level text without lock status
var levelText = new Text2(levelNames[i], {
size: 50,
fill: 0xFFFFFF
});
levelText.anchor.set(0.5, 0.5);
levelText.x = levelBox.x;
levelText.y = levelBox.y;
game.addChild(levelText);
levelBox.levelText = levelText;
levelBox.isUnlocked = isUnlocked;
}
}
// Create shop screen
function createShop() {
gameState = 'shop';
// Hide other UI elements
titleText.alpha = 0;
playButton.alpha = 0;
instructionText.alpha = 0;
// Shop title
var shopTitle = new Text2('TIENDA DE MEJORAS', {
size: 120,
fill: 0xFFD700
});
shopTitle.anchor.set(0.5, 0.5);
shopTitle.x = 1024;
shopTitle.y = 400;
game.addChild(shopTitle);
// Show current spheres
var spheresDisplay = new Text2('Esferas: ' + totalSpheres, {
size: 80,
fill: 0xFFFFFF
});
spheresDisplay.anchor.set(0.5, 0.5);
spheresDisplay.x = 1024;
spheresDisplay.y = 500;
game.addChild(spheresDisplay);
// Generate shop items with fixed 4 slots (no rarity system)
var shopItemsData = [];
// Calculate centered positions for 3 shop items
var itemWidth = 300; // Width of each shop item box (scaled)
var itemSpacing = 150; // Spacing between items
var totalWidth = 3 * itemWidth + 2 * itemSpacing; // Total width of all items plus spacing
var startX = (2048 - totalWidth) / 2 + itemWidth / 2; // Center the entire layout
// Slot 1: Multiplicador de Esferas (Fixed)
var sphereMultItem = {
name: 'Multiplicador\nde Esferas',
description: 'x' + (sphereMultiplier + 0.2).toFixed(1),
price: 200 + Math.floor(100 * purchaseCounts.sphere_multiplier),
type: 'sphere_multiplier',
x: startX,
y: 700
};
// Slot 2: Multiplicador de Velocidad (Fixed)
var speedMultItem = {
name: 'Multiplicador\nde Velocidad',
description: '+5% Velocidad',
price: 111 + Math.floor(60 * purchaseCounts.speed_multiplier),
type: 'speed_multiplier',
x: startX + itemWidth + itemSpacing,
y: 700
};
// Slot 3: Aumento de Vida Max (Fixed)
var healthBonusItem = {
name: 'Aumento\nde Vida Max',
description: '+5 Vida Max',
price: 91 + Math.floor(50 * purchaseCounts.health_bonus),
type: 'health_bonus',
x: startX + 2 * (itemWidth + itemSpacing),
y: 700
};
shopItemsData.push(sphereMultItem);
shopItemsData.push(speedMultItem);
shopItemsData.push(healthBonusItem);
for (var i = 0; i < shopItemsData.length; i++) {
var itemData = shopItemsData[i];
// Create item box (expanded)
var itemBox = LK.getAsset('level1', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
itemBox.x = itemData.x;
itemBox.y = itemData.y;
itemBox.itemType = itemData.type;
itemBox.price = itemData.price;
itemBox.tint = 0x00FF00;
game.addChild(itemBox);
shopItems.push(itemBox);
// Item name
var nameText = new Text2(itemData.name, {
size: 55,
fill: 0xFFFFFF
});
nameText.anchor.set(0.5, 0.5);
nameText.x = itemData.x;
nameText.y = itemData.y - 40;
game.addChild(nameText);
itemBox.nameText = nameText;
// Item price
var priceText = new Text2('Precio: ' + itemData.price, {
size: 50,
fill: 0xFFFFFF
});
priceText.anchor.set(0.5, 0.5);
priceText.x = itemData.x;
priceText.y = itemData.y + 40;
game.addChild(priceText);
itemBox.priceText = priceText;
}
// Exit button (changed from Continue)
var exitBtn = new Text2('SALIR', {
size: 80,
fill: 0x00FF00
});
exitBtn.anchor.set(0.5, 0.5);
exitBtn.x = 1024;
exitBtn.y = 1400; // Moved down by 200px
game.addChild(exitBtn);
shopItems.push(exitBtn);
exitBtn.isContinueBtn = true;
// Skip button
var skipBtn = new Text2('SALTAR', {
size: 80,
fill: 0x00FF00
});
skipBtn.anchor.set(0.5, 0.5);
skipBtn.x = 1024;
skipBtn.y = 1500; // Moved down by 200px
game.addChild(skipBtn);
shopItems.push(skipBtn);
skipBtn.isSkipBtn = true;
// Shop timer display
shopTimerText = new Text2('Tiempo: 10', {
size: 80,
fill: 0xFF0000
});
shopTimerText.anchor.set(0.5, 0.5);
shopTimerText.x = 1024;
shopTimerText.y = 1200; // Moved up to make room for buttons
game.addChild(shopTimerText);
shopItems.push(shopTimerText);
// Start shop timer
shopTimer = 10;
shopTimerActive = true;
shopTimerInterval = LK.setInterval(function () {
shopTimer--;
shopTimerText.setText('Tiempo: ' + shopTimer);
if (shopTimer <= 0) {
LK.clearInterval(shopTimerInterval);
shopTimerActive = false;
// Auto-proceed when timer expires
// Clear shop UI
for (var j = shopItems.length - 1; j >= 0; j--) {
if (shopItems[j].nameText) shopItems[j].nameText.destroy();
if (shopItems[j].priceText) shopItems[j].priceText.destroy();
shopItems[j].destroy();
}
shopItems = [];
// Clear shop title and spheres display
for (var k = game.children.length - 1; k >= 0; k--) {
if (game.children[k].text && (game.children[k].text.indexOf('TIENDA') === 0 || game.children[k].text.indexOf('Esferas:') === 0)) {
tween(game.children[k], {
alpha: 0
}, {
duration: 1000
});
LK.setTimeout(function (element) {
return function () {
if (element && element.destroy) {
element.destroy();
}
};
}(game.children[k]), 1000);
}
}
// Go to countdown
startCountdown();
}
}, 1000);
}
// Start character selection
function startCharacterSelection() {
gameState = 'characterSelection';
// Hide menu elements
titleText.alpha = 0;
playButton.alpha = 0;
// Character selection instruction text removed to avoid interference
// Spawn character selection spheres
spawnCharacterSelectionSpheres();
}
// Start countdown before game
function startCountdown() {
gameState = 'countdown';
countdownActive = true;
countdownValue = 3;
countdownText.showWithTimeout();
countdownText.setText('3');
var countdownInterval = LK.setInterval(function () {
countdownValue--;
if (countdownValue > 0) {
countdownText.setText(countdownValue.toString());
} else if (countdownValue === 0) {
countdownText.setText('¡Juega!');
} else {
LK.clearInterval(countdownInterval);
countdownText.alpha = 0;
countdownText.visible = false;
startActualGame();
}
}, 1000);
}
// Start the actual game
function startActualGame() {
gameState = 'playing';
countdownActive = false;
gameStarted = true;
// Initialize timer
gameTimer = 223;
timerActive = true;
speedIncreased = false;
// Show menu button during gameplay
menuButton.alpha = 1;
menuButtonText.alpha = 1;
// Hide all level selection boxes during gameplay
for (var i = 0; i < levelBoxes.length; i++) {
levelBoxes[i].alpha = 0;
if (levelBoxes[i].levelText) {
levelBoxes[i].levelText.alpha = 0;
}
}
// Hide any shop elements that might still be visible during gameplay
for (var shopCleanup = game.children.length - 1; shopCleanup >= 0; shopCleanup--) {
if (game.children[shopCleanup].text && (game.children[shopCleanup].text.indexOf('TIENDA') === 0 || game.children[shopCleanup].text.indexOf('Esferas:') === 0 && game.children[shopCleanup] !== sphereCountText)) {
game.children[shopCleanup].alpha = 0;
game.children[shopCleanup].visible = false;
}
}
// Hide all shop-related elements during active gameplay
for (var shopCleanup = game.children.length - 1; shopCleanup >= 0; shopCleanup--) {
var child = game.children[shopCleanup];
if (child.text && (child.text.indexOf('TIENDA DE MEJORAS') === 0 || child.text.indexOf('Esferas:') === 0 && child !== sphereCountText)) {
child.alpha = 0;
child.visible = false;
}
}
// Show/hide UI elements based on level
if (currentLevel === 1) {
// Level 1: Show health, score, timer, sphere counter
scoreText.alpha = 1;
healthText.alpha = 1;
timerText.alpha = 1;
heightText.alpha = 0;
sphereCountText.alpha = 1;
} else if (currentLevel === 2) {
// Level 2: Show only distance and score
scoreText.alpha = 1;
healthText.alpha = 0;
timerText.alpha = 0;
heightText.alpha = 1;
sphereCountText.alpha = 0; // sphereCountText no longer auto-hides
} else if (currentLevel === 3) {
// Level 3: Show score and missed shots counter
scoreText.alpha = 1;
healthText.alpha = 0;
timerText.alpha = 0;
heightText.alpha = 0;
sphereCountText.alpha = 0;
missedShotsText.alpha = 1;
} else if (currentLevel === 4) {
// Level 4: Show only score
scoreText.alpha = 1;
healthText.alpha = 0;
timerText.alpha = 0;
heightText.alpha = 0;
sphereCountText.alpha = 0;
missedShotsText.alpha = 0;
} else {
// Other levels: Show score and sphere counter
scoreText.alpha = 1;
healthText.alpha = 0;
timerText.alpha = 0;
heightText.alpha = 0;
sphereCountText.alpha = 1;
}
// Create player character
if (currentLevel === 3) {
// Level 3: Create bubble shooter instead of character
bubbleShooter = new BubbleShooter();
bubbleShooter.initGrid();
bubbleShooter.createInitialBubbles();
game.addChild(bubbleShooter);
// Create cannon and add to bubbleShooter container
var cannon = LK.getAsset('cannon', {
anchorX: 0.5,
anchorY: 1
});
cannon.x = 1024;
cannon.y = 2600;
bubbleShooter.cannon = cannon;
bubbleShooter.addChild(cannon); // Add cannon to bubbleShooter instead of game
// Create initial bubbles after setting up cannon
bubbleShooter.createNewBubble();
} else if (currentLevel === 4) {
// Level 4: Create Snake Game
var snakeGame = new SnakeGame();
snakeGame.initGame();
game.addChild(snakeGame);
// Store reference for input handling
game.snakeGame = snakeGame;
} else {
character = new EmotionCharacter(selectedEmotion);
if (currentLevel === 2) {
// Spawn in middle of screen for Flappy Bird level
character.x = 1024;
character.y = 1366; // Middle of screen vertically
// Reset level 2 specific variables
birdVelocity = 0;
distanceTraveled = 0;
scoreCounter = 0;
// Clear any existing pipes
for (var p = pipes.length - 1; p >= 0; p--) {
pipes[p].destroy();
}
pipes = [];
} else {
character.x = 1024;
character.y = 2400;
// Reset level 1 specific variables with upgrades
health = 100 + maxHealthBonus;
maxHealth = 100 + maxHealthBonus;
healthText.setText('Vida: ' + health + '/' + maxHealth);
// Clear any existing memories
for (var g = goldenMemories.length - 1; g >= 0; g--) {
goldenMemories[g].destroy();
}
goldenMemories = [];
for (var c = corruptedMemories.length - 1; c >= 0; c--) {
corruptedMemories[c].destroy();
}
corruptedMemories = [];
}
game.addChild(character);
}
// Start level-specific background music
if (currentLevel === 2) {
LK.playMusic('musica_de_nivel_2');
} else {
LK.playMusic('gameMusic');
}
}
// Spawn memory function
function spawnMemory() {
if (gameState === 'characterSelection') {
// Character selection phase - spawn static character spheres in center
spawnCharacterSelectionSpheres();
} else if (gameState === 'playing') {
// Game phase - spawn colored spheres and corrupted ones
var spawnX = Math.random() * (2048 - 160) + 80;
var randomType = Math.random();
// Define sphere colors for optimization
var sphereColors = [0xffeb3b, 0x4caf50, 0x2196f3, 0xf44336];
var colorThresholds = [0.15, 0.3, 0.45, 0.6];
// Calculate time-based spine multiplier - more spines as time progresses
var initialTimer = 223;
var timeElapsed = initialTimer - gameTimer;
var timeProgressRatio = Math.min(timeElapsed / initialTimer, 1); // 0 to 1 progression
// Base spine spawn probability increases with time (starts at 0.33, goes up to 0.85)
var baseSpineProbability = 0.33 + timeProgressRatio * 0.52;
// Check for golden memory creation - probability decreases over time to allow for more spines
var goldenProbability = 0.67 * (1 - timeProgressRatio * 0.6); // Decreases from 0.67 to ~0.27
var createGolden = false;
var tintColor = 0xffeb3b;
for (var i = 0; i < colorThresholds.length; i++) {
if (randomType < colorThresholds[i] * goldenProbability) {
createGolden = true;
tintColor = sphereColors[i];
break;
}
}
if (createGolden) {
var memory = new GoldenMemory();
memory.x = spawnX;
memory.y = -100;
memory.lastY = memory.y;
memory.children[0].tint = tintColor;
goldenMemories.push(memory);
game.addChild(memory);
} else {
// Corrupted memory with spikes - spawn rate increases dramatically over time
var corrupted = new CorruptedMemory();
corrupted.x = spawnX;
corrupted.y = -100;
corrupted.lastY = corrupted.y;
corruptedMemories.push(corrupted);
game.addChild(corrupted);
}
}
}
// Game event handlers
game.down = function (x, y, obj) {
if (gameState === 'menu') {
// Check if JUGAR button was clicked with invisible hitbox
var playButtonX = playButton.x - playButton.width / 2;
var playButtonY = playButton.y - playButton.height / 2;
var playButtonWidth = playButton.width;
var playButtonHeight = playButton.height;
if (x >= playButtonX && x <= playButtonX + playButtonWidth && y >= playButtonY && y <= playButtonY + playButtonHeight) {
createLevelSelection();
}
} else if (gameState === 'levelSelection') {
// Check if any level box was clicked
for (var i = 0; i < levelBoxes.length; i++) {
var levelBox = levelBoxes[i];
var boxX = levelBox.x - 150; // Half of width (300/2)
var boxY = levelBox.y - 100; // Half of height (200/2)
var boxWidth = 300;
var boxHeight = 200;
if (x >= boxX && x <= boxX + boxWidth && y >= boxY && y <= boxY + boxHeight) {
// All levels are unlocked - no need to check
// Flash selected level
LK.effects.flashObject(levelBox, 0xFFFFFF, 500);
// Set current level
currentLevel = levelBox.levelNumber;
storage.currentLevel = currentLevel;
// Clear level selection UI
for (var j = levelBoxes.length - 1; j >= 0; j--) {
if (levelBoxes[j].levelText) {
levelBoxes[j].levelText.destroy();
}
levelBoxes[j].destroy();
}
levelBoxes = [];
// Level selection title already removed, no cleanup needed
// Start character selection after short delay
LK.setTimeout(function () {
startCharacterSelection();
}, 600);
break;
}
}
} else if (gameState === 'shop') {
// Handle shop item clicks
for (var i = 0; i < shopItems.length; i++) {
var item = shopItems[i];
var itemX = item.x - 150;
var itemY = item.y - 100;
var itemWidth = 300;
var itemHeight = 200;
if (x >= itemX && x <= itemX + itemWidth && y >= itemY && y <= itemY + itemHeight) {
if (item.isContinueBtn || item.isSkipBtn) {
// Clear shop timer
if (shopTimerInterval) {
LK.clearInterval(shopTimerInterval);
shopTimerActive = false;
}
// Clear shop UI
for (var j = shopItems.length - 1; j >= 0; j--) {
if (shopItems[j].nameText) shopItems[j].nameText.destroy();
if (shopItems[j].priceText) shopItems[j].priceText.destroy();
shopItems[j].destroy();
}
shopItems = [];
// Clear shop title and spheres display
for (var k = game.children.length - 1; k >= 0; k--) {
if (game.children[k].text && (game.children[k].text.indexOf('TIENDA') === 0 || game.children[k].text.indexOf('Esferas:') === 0)) {
game.children[k].destroy();
}
}
if (item.isSkipBtn) {
// Skip shop timer and go directly to countdown
startCountdown();
} else {
// Exit button - go directly to countdown
startCountdown();
}
break;
} else if (totalSpheres >= item.price) {
// Check purchase limit
if (purchaseCounts[item.itemType] >= 20) {
// Show max purchases message
var maxText = new Text2('Máximo de compras alcanzado', {
size: 60,
fill: 0xFF0000
});
maxText.anchor.set(0.5, 0.5);
maxText.x = 1024;
maxText.y = 1000;
game.addChild(maxText);
LK.setTimeout(function () {
maxText.destroy();
}, 2000);
LK.getSound('error').play();
} else {
// Purchase item
totalSpheres -= item.price;
storage.totalSpheres = totalSpheres;
purchaseCounts[item.itemType]++;
storage.purchaseCounts = purchaseCounts;
if (item.itemType === 'sphere_multiplier') {
sphereMultiplier += 0.2;
storage.sphereMultiplier = sphereMultiplier;
} else if (item.itemType === 'speed_multiplier') {
speedMultiplier += 0.05;
storage.speedMultiplier = speedMultiplier;
} else if (item.itemType === 'health_bonus') {
maxHealthBonus += 5;
storage.maxHealthBonus = maxHealthBonus;
}
}
LK.effects.flashObject(item, 0x00FF00, 300);
LK.getSound('collect').play();
// Clear shop timer before recreating
if (shopTimerInterval) {
LK.clearInterval(shopTimerInterval);
shopTimerActive = false;
}
// Recreate shop to update prices
for (var j = shopItems.length - 1; j >= 0; j--) {
if (shopItems[j].nameText) shopItems[j].nameText.destroy();
if (shopItems[j].priceText) shopItems[j].priceText.destroy();
shopItems[j].destroy();
}
shopItems = [];
for (var k = game.children.length - 1; k >= 0; k--) {
if (game.children[k].text && (game.children[k].text.indexOf('TIENDA') === 0 || game.children[k].text.indexOf('Esferas:') === 0)) {
game.children[k].destroy();
}
}
createShop();
break;
} else {
LK.effects.flashObject(item, 0xFF0000, 300);
LK.getSound('error').play();
}
}
}
} else if (gameState === 'characterSelection') {
// Check if any character sphere was clicked with invisible hitboxes
for (var i = 0; i < goldenMemories.length; i++) {
var sphere = goldenMemories[i];
var sphereX = sphere.x - 60; // Sphere width is 120, so half is 60
var sphereY = sphere.y - 60; // Sphere height is 120, so half is 60
var sphereWidth = 120;
var sphereHeight = 120;
if (x >= sphereX && x <= sphereX + sphereWidth && y >= sphereY && y <= sphereY + sphereHeight) {
handleSphereSelection(sphere);
break;
}
}
} else if (gameState === 'playing') {
// Check if menu button was clicked (adjusted for bottom right anchor)
var menuButtonX = menuButton.x - 240; // Full scaled width (anchored to right)
var menuButtonY = menuButton.y - 120; // Full scaled height (anchored to bottom)
var menuButtonWidth = 240; // Scaled width
var menuButtonHeight = 120; // Scaled height
if (x >= menuButtonX && x <= menuButtonX + menuButtonWidth && y >= menuButtonY && y <= menuButtonY + menuButtonHeight) {
// Stop any music
LK.stopMusic();
// Hide menu button
menuButton.alpha = 0;
menuButtonText.alpha = 0;
// Hide all UI elements
scoreText.alpha = 0;
healthText.alpha = 0;
timerText.alpha = 0;
heightText.alpha = 0;
sphereCountText.alpha = 0;
// Destroy character if exists
if (character) {
character.destroy();
character = null;
}
// Clear all game objects
for (var i = goldenMemories.length - 1; i >= 0; i--) {
goldenMemories[i].destroy();
}
goldenMemories = [];
for (var i = corruptedMemories.length - 1; i >= 0; i--) {
corruptedMemories[i].destroy();
}
corruptedMemories = [];
for (var i = pipes.length - 1; i >= 0; i--) {
pipes[i].destroy();
}
pipes = [];
for (var i = sideSpikes.length - 1; i >= 0; i--) {
sideSpikes[i].destroy();
}
sideSpikes = [];
// Clean up bubble shooter
if (bubbleShooter) {
bubbleShooter.destroy();
bubbleShooter = null;
}
// Clean up snake game
if (game.snakeGame) {
game.snakeGame.destroy();
game.snakeGame = null;
}
// Hide missed shots counter
missedShotsText.alpha = 0;
// Clean up aim guide
for (var i = aimGuide.length - 1; i >= 0; i--) {
aimGuide[i].destroy();
}
aimGuide = [];
// Reset game variables
gameStarted = false;
timerActive = false;
speedIncreased = false;
// Show menu elements
titleText.alpha = 1;
playButton.alpha = 1;
// Go to level selection
createLevelSelection();
}
if (currentLevel === 2) {
// Handle flapping in lvl2 - Flappy Bird style
if (character) {
birdVelocity = flapStrength;
}
} else if (currentLevel === 3 && bubbleShooter) {
// Level 3: Bubble shooting
if (bubbleShooter.currentBubble && bubbleShooter.canShoot) {
// Get fresh aim angle at moment of shooting
var cannonX = bubbleShooter.cannon.x;
var cannonY = bubbleShooter.cannon.y;
var freshAimAngle = Math.atan2(y - cannonY, x - cannonX);
// Limit aiming angle to upward directions only (allow wider range for better shooting)
if (freshAimAngle > -Math.PI * 0.9 && freshAimAngle < -Math.PI * 0.1) {
// Use shootBubble method for proper physics
bubbleShooter.shootBubble(freshAimAngle);
}
}
} else if (currentLevel === 4 && game.snakeGame) {
// Level 4: Snake Game mouse click control
// Convert click position to grid coordinates
var floorLeft = game.snakeGame.floor.x - 1800 / 2;
var floorTop = game.snakeGame.floor.y - 2400 / 2;
var targetGridX = Math.floor((x - floorLeft) / game.snakeGame.gridSize);
var targetGridY = Math.floor((y - floorTop) / game.snakeGame.gridSize);
// Ensure target is within grid bounds
if (targetGridX >= 0 && targetGridX < game.snakeGame.gridWidth && targetGridY >= 0 && targetGridY < game.snakeGame.gridHeight) {
var currentHead = game.snakeGame.snake[0];
var dx = targetGridX - currentHead.x;
var dy = targetGridY - currentHead.y;
// Determine movement direction based on click position
// Prioritize the direction with the larger distance
if (Math.abs(dx) >= Math.abs(dy) && dx !== 0) {
// Horizontal movement has priority
if (dx > 0 && game.snakeGame.direction !== 'left') {
game.snakeGame.nextDirection = 'right';
} else if (dx < 0 && game.snakeGame.direction !== 'right') {
game.snakeGame.nextDirection = 'left';
}
} else if (dy !== 0) {
// Vertical movement
if (dy > 0 && game.snakeGame.direction !== 'up') {
game.snakeGame.nextDirection = 'down';
} else if (dy < 0 && game.snakeGame.direction !== 'down') {
game.snakeGame.nextDirection = 'up';
}
}
}
}
}
};
game.up = function (x, y, obj) {};
game.move = function (x, y, obj) {
if (gameStarted) {
if (currentLevel === 2 && character) {
// In lvl2, character stays in middle horizontal position for Flappy Bird
character.x = 1024;
// Y position is handled by gravity and flapping in update
} else if (currentLevel === 3 && bubbleShooter) {
// Level 3: Update aiming
if (bubbleShooter.cannon) {
var cannonX = bubbleShooter.cannon.x;
var cannonY = bubbleShooter.cannon.y;
aimAngle = Math.atan2(y - cannonY, x - cannonX);
// Limit aiming angle to upward directions only (allow wider range for better aiming)
if (aimAngle > -Math.PI * 0.9 && aimAngle < -Math.PI * 0.1) {
bubbleShooter.cannon.rotation = aimAngle + Math.PI / 2;
// Move current bubble to follow cannon aiming
if (bubbleShooter.currentBubble && bubbleShooter.canShoot) {
var aimDistance = 80; // Distance from cannon tip
bubbleShooter.currentBubble.x = cannonX + Math.cos(aimAngle) * aimDistance;
bubbleShooter.currentBubble.y = cannonY + Math.sin(aimAngle) * aimDistance;
}
}
}
} else if (character) {
// Original behavior for lvl1
var targetX = Math.max(60, Math.min(1988, x));
var targetY = Math.max(60, Math.min(2672, y));
character.x = targetX;
character.y = targetY;
}
}
};
// Start spawning spheres for character selection
// Main game update loop
game.update = function () {
// Handle different game states
if (gameState === 'menu') {
// Stop any playing music in menu
LK.stopMusic();
return; // No updates needed in menu
}
if (gameState === 'levelSelection') {
// Static level boxes, no updates needed
return;
}
if (gameState === 'characterSelection') {
// Static spheres, no updates needed
return;
}
if (gameState === 'countdown') {
return; // Countdown handles itself
}
if (gameState !== 'playing') {
return;
}
// Update sphere counter display
sphereCountText.setText('Esferas: ' + totalSpheres);
// Update timer
if (timerActive && LK.ticks % 60 === 0) {
// Update every second (60 ticks = 1 second at 60 FPS)
gameTimer--;
timerText.setText('Tiempo: ' + gameTimer);
// When timer reaches 0, increase speed
if (gameTimer <= 0 && !speedIncreased) {
speedIncreased = true;
timerActive = false;
// Speed increase will be handled in the speed calculation section below
}
}
// Game playing state - spawn spheres or spikes based on level
spawnTimer++;
if (currentLevel === 2) {
// Level 2: Flappy Bird mechanics - completely separate from other levels
// Spawn pipes
var pipeSpawnRate = 240; // Spawn pipe every 4 seconds (increased from 2 seconds for more spacing)
if (spawnTimer >= pipeSpawnRate) {
var gapY = Math.random() * (2000 - 700) + 700; // Random gap position between y 700-2000
// Create top pipe
var topPipe = new Pipe(true, gapY);
topPipe.x = 2100;
topPipe.y = gapY - pipeGap / 2;
topPipe.lastX = topPipe.x;
pipes.push(topPipe);
game.addChild(topPipe);
// Create bottom pipe
var bottomPipe = new Pipe(false, gapY);
bottomPipe.x = 2100;
bottomPipe.y = gapY + pipeGap / 2;
bottomPipe.lastX = bottomPipe.x;
pipes.push(bottomPipe);
game.addChild(bottomPipe);
spawnTimer = 0;
}
// Handle Flappy Bird physics
if (character) {
// Apply gravity
birdVelocity += gravity;
character.y += birdVelocity;
// Check boundaries - only bounce off top, die on bottom and left
if (character.y < 50) {
character.y = 50;
birdVelocity = 0;
}
// No win condition for minigame 2 - play until you lose
// Check death conditions: left edge or bottom
if (character.x <= 0 || character.y > 2680) {
// Hit left edge or ground - game over
attemptCount++;
storage.attemptCount = attemptCount;
// Track level-specific attempts
if (!levelAttempts[currentLevel]) {
levelAttempts[currentLevel] = 0;
}
levelAttempts[currentLevel]++;
storage.levelAttempts = levelAttempts;
// Convert distance to spheres (divide by 10)
var spheresEarned = Math.floor(distanceTraveled / 10);
totalSpheres += spheresEarned;
storage.totalSpheres = totalSpheres;
LK.showGameOver();
return;
}
// Update distance
distanceTraveled += 3; // Distance increases as pipes move (matches pipe speed)
heightText.setText('Distancia: ' + Math.floor(distanceTraveled) + 'm');
}
// Update pipes and check collisions
for (var p = pipes.length - 1; p >= 0; p--) {
var pipe = pipes[p];
if (pipe.lastX === undefined) pipe.lastX = pipe.x;
var shouldRemovePipe = false;
// Check if off screen
if (pipe.lastX >= -pipeWidth && pipe.x < -pipeWidth) {
shouldRemovePipe = true;
}
// Check collision with character - bounce instead of game over
if (character && pipe.intersects(character)) {
// Bounce character away from pipe
if (pipe.isTop) {
// Hit top pipe - bounce down
character.y = pipe.y + pipe.height + 20;
birdVelocity = 5; // Push down
} else {
// Hit bottom pipe - bounce up
character.y = pipe.y - 20;
birdVelocity = -5; // Push up
}
// Flash character red to indicate collision
LK.effects.flashObject(character, 0xFF0000, 300);
}
// Check if passed pipe for scoring (only check bottom pipes to avoid double scoring)
if (!pipe.isTop && !pipe.scored && pipe.lastX >= character.x && pipe.x < character.x) {
pipe.scored = true;
scoreCounter++;
LK.setScore(scoreCounter);
scoreText.setText('Puntos: ' + LK.getScore());
LK.getSound('collect').play();
}
if (shouldRemovePipe) {
pipe.destroy();
pipes.splice(p, 1);
} else {
pipe.lastX = pipe.x;
}
}
// Level 2 specific updates only
return; // Skip level 1 logic for level 2
} else if (currentLevel === 3) {
// Level 3: Bubble Shooter logic
if (bubbleShooter) {
// Add new rows periodically
if (LK.ticks % 1800 === 0) {
// Every 30 seconds
bubbleShooter.addNewRowFromTop();
}
// No win condition - game continues indefinitely
// Bubble shooter physics will be handled in the class update method
}
return; // Skip other level logic
} else if (currentLevel === 4) {
// Level 4: Snake Game logic
if (game.snakeGame) {
// Snake game handles its own updates
// No additional spawning or logic needed
}
return; // Skip other level logic
} else if (currentLevel === 1) {
// Level 1 logic - completely separate from other levels
var spawnRate = Math.max(30, 90 - difficultyLevel * 5);
if (spawnTimer >= spawnRate) {
spawnMemory();
spawnTimer = 0;
}
}
// Increase difficulty over time
if (LK.getScore() > 0 && LK.getScore() % 10 === 0) {
difficultyLevel = Math.floor(LK.getScore() / 10) + 1;
}
// Spawn rate already handled at top of update function
// Only update memories for level 1
if (currentLevel === 1) {
// Update golden memories
for (var i = goldenMemories.length - 1; i >= 0; i--) {
var golden = goldenMemories[i];
// Initialize lastY if needed
if (golden.lastY === undefined) golden.lastY = golden.y;
var shouldRemove = false;
// Check if off screen
if (golden.lastY <= 2732 && golden.y > 2732) {
shouldRemove = true;
} else if (character && gameStarted && golden.intersects(character)) {
// Check collision with character for game phase
var spherePoints = Math.floor(3 * sphereMultiplier);
// Disgust character gets 2x spheres
if (selectedEmotion === 'disgust') {
spherePoints = Math.floor(6 * sphereMultiplier);
}
LK.setScore(LK.getScore() + spherePoints);
scoreText.setText('Puntos: ' + LK.getScore());
LK.getSound('collect').play();
// Flash character gold
LK.effects.flashObject(character, 0xFFD700, 300);
shouldRemove = true;
} else if (!gameStarted && !countdownActive && golden.characterType && golden.y > 500 && golden.y < 2000) {
// Check collision for character selection phase
handleSphereSelection(golden);
shouldRemove = true;
}
if (shouldRemove) {
golden.destroy();
goldenMemories.splice(i, 1);
} else {
golden.lastY = golden.y;
}
}
// Update corrupted memories
for (var j = corruptedMemories.length - 1; j >= 0; j--) {
var corrupted = corruptedMemories[j];
// Initialize lastY if needed
if (corrupted.lastY === undefined) corrupted.lastY = corrupted.y;
var shouldRemove = false;
// Check if off screen
if (corrupted.lastY <= 2732 && corrupted.y > 2732) {
shouldRemove = true;
} else if (character && corrupted.intersects(character)) {
// Check collision with character
var damage = speedIncreased ? 25 : 20;
health -= damage;
healthText.setText('Vida: ' + health + '/' + maxHealth);
LK.getSound('damage').play();
// Flash character red
LK.effects.flashObject(character, 0xFF0000, 500);
shouldRemove = true;
// Check game over
if (health <= 0) {
// Stop character from following cursor
if (character) {
tween.stop(character);
}
// Increment attempt counter
attemptCount++;
storage.attemptCount = attemptCount;
// Track level-specific attempts
if (!levelAttempts[currentLevel]) {
levelAttempts[currentLevel] = 0;
}
levelAttempts[currentLevel]++;
storage.levelAttempts = levelAttempts;
// Add accumulated points to total spheres
totalSpheres += LK.getScore();
storage.totalSpheres = totalSpheres;
// Show game over immediately
LK.showGameOver();
return;
}
}
if (shouldRemove) {
corrupted.destroy();
corruptedMemories.splice(j, 1);
} else {
corrupted.lastY = corrupted.y;
}
}
}
// Calculate speed only for level 1
if (currentLevel === 1) {
var goldenBaseSpeed = (3 + difficultyLevel * 0.5) * speedMultiplier;
var corruptedBaseSpeed = (4 + difficultyLevel * 0.5) * speedMultiplier;
if (speedIncreased) {
goldenBaseSpeed += goldenBaseSpeed * 0.5;
corruptedBaseSpeed += corruptedBaseSpeed * 0.5;
}
// Double speed for lvl1
goldenBaseSpeed *= 2;
corruptedBaseSpeed *= 2;
// Apply speed boost multiplier if active
if (speedBoostActive) {
goldenBaseSpeed *= 2;
corruptedBaseSpeed *= 2;
}
// Apply calculated speeds
for (var k = 0; k < goldenMemories.length; k++) {
goldenMemories[k].speed = goldenBaseSpeed;
}
for (var l = 0; l < corruptedMemories.length; l++) {
corruptedMemories[l].speed = corruptedBaseSpeed;
}
}
// Win condition removed - game continues indefinitely
// Win condition removed - game continues indefinitely
};