User prompt
🛠️ Corrección: Cobertura COMPLETA de Burbujas Iniciales en el Techo (Nivel 3) Problema Actual (como se ve en la imagen): Las burbujas generadas por self.createInitialBubbles() están agrupadas en el centro superior de la pantalla, dejando grandes espacios vacíos a los lados. Necesitan cubrir todo el ancho de la parte superior, desde el borde izquierdo al derecho. Instrucción para la IA: Ajustar la Función self.createInitialBubbles() para Cobertura Total: Re-calcular self.gridCols y self.bubbleRadius: Es fundamental que el self.gridCols (número de columnas) en combinación con el self.bubbleRadius (radio de la burbuja) permitan que la grilla hexagonal se extienda completamente a lo largo de los 2048px de ancho de la pantalla. Si self.bubbleRadius es 65 (diámetro 130), y self.gridCols es 8, entonces el ancho total de 8 burbujas es 8 * 130 = 1040px, más el offset hexagonal. Esto es claramente insuficiente para 2048px. Acción Requerida: Aumenta self.gridCols (por ejemplo, a 16 columnas o el valor que sea necesario) y/o reduce self.bubbleRadius para que el ancho total de la grilla hexagonal cubra los 2048px. Calcula el bubbleRadius necesario para gridCols = 16 para cubrir los 2048px. Por ejemplo, 2048 / (16 * 2) = 64px si es un patrón de cuadrados, pero para hexágonos es más complejo. Ajustar self.getHexPosition() para Centrado Correcto: Una vez que self.gridCols y self.bubbleRadius estén optimizados para el ancho, ajusta self.getHexPosition(row, col) para centrar horizontalmente toda la grilla. Actualmente, var x = self.bubbleRadius + col * (self.bubbleRadius * 2) + offsetX; no tiene un offset de centrado para el ancho total. Acción Requerida: Calcula el ancho total real de la grilla hexagonal y resta la mitad de ese ancho al valor x para centrarla en 2048 / 2 = 1024px. Bucle de Generación: El bucle for (var col = 0; col < self.gridCols - row % 2; col++) es correcto para la forma hexagonal, pero debe aplicarse al nuevo y mayor self.gridCols para garantizar que la generación se extienda por toda la pantalla. En Resumen: La IA debe realizar un recálculo de self.gridCols y self.bubbleRadius para llenar 2048px, y luego ajustar el x inicial en self.getHexPosition para centrar esa grilla extendida.
User prompt
🛠️ Modificaciones Urgentes en la Pantalla de la Tienda de Mejoras 1. Eliminación de la Condición de Victoria/Lógica de Ganar CRÍTICO: La tienda debe ser una pausa temporal, no una condición de fin de juego o victoria. Es probable que un self.checkWin() o una función de game over se esté ejecutando al entrar a la tienda. Instrucción para la IA: Desactiva o Elimina cualquier Lógica de Fin de Juego al entrar a la Tienda: Asegúrate de que, cuando el gameState se establezca en 'shop', no se ejecute ninguna función de chequeo de victoria (self.checkWin()) o de derrota (self.checkLose()). La tienda es una pausa. La única forma de salir de esta pantalla debe ser a través de los botones 'SALIR' o 'SALTAR'. 2. Ajustes de Interfaz de Usuario (UI) Necesitas corregir la posición y el texto de los botones. Instrucción para la IA: Ajustar la Posición y el Texto de los Botones Inferiores: Cambiar Texto del Botón: Modifica el texto del botón 'CONTINUAR' para que ahora diga 'SALIR'. Bajar Posición: Mueve los dos botones inferiores ('SALIR' y 'SALTAR') hacia abajo, aproximadamente 150 a 200 píxeles en el eje 'Y' para que no estén tan cerca del texto del temporizador. Ejemplo de ajuste de coordenadas (asumiendo que están alrededor de y=1800): botonSalir.y = 2000; botonSaltar.y = 2150; (Aumentar el valor de 'y' en 200 píxeles). Con estos cambios, la Tienda de Mejoras funcionará correctamente como una pausa sin causar errores de fin de juego, y su interfaz será más clara.
User prompt
🛠️ Instrucciones Urgentes para el Minijuego 3 (Bubble Shooter) 1. Corrección: Cobertura de la Grilla Inicial (Burbujas en el Techo) La grilla inicial está mal posicionada y no está cubriendo todo el ancho de la pantalla (2048px), como se ve en la imagen proporcionada. Problema: La lógica de coordenadas y la iteración por columna (col) no se ajustan correctamente a la grilla hexagonal de tu pantalla de 2048px. Instrucción para la IA: Modificar la función self.createInitialBubbles(): Asegurar el Centrado: Revisa la función self.getHexPosition(row, col) y ajusta el self.bubbleRadius para que el espaciado hexagonal (self.bubbleRadius * 2 para columnas) permita que la grilla de 8 columnas cubra los 2048px. Puede que necesites reducir el bubbleRadius o ajustar un offset inicial. Corregir la Iteración: Asegúrate de que el bucle de la columna (col) itere correctamente sobre el número máximo de columnas permitido para esa fila (8 columnas para filas pares, 7 para impares). JavaScript // CÓDIGO SUGERIDO PARA AJUSTAR LA ITERACIÓN EN createInitialBubbles // (Basado en tu código de 8 columnas) self.createInitialBubbles = function () { for (var row = 0; row < 6; row++) { // La grilla hexagonal tiene un patrón de N y N-1 columnas (ej. 8, 7, 8, 7...) var colsInRow = self.gridCols - (row % 2); // 8 o 7 columnas for (var col = 0; col < colsInRow; col++) { // ... (El resto de la lógica de creación de burbujas permanece igual) if (Math.random() < 0.8) { // ... } } } }; 2. Corrección: Sistema de Presión (Movimiento Constante de la Grilla) Las burbujas iniciales no se mueven porque solo tienes implementado el movimiento por fallos (self.moveGridDown()). Para que la grilla "escale hacia abajo" constantemente, necesitas aplicar un sistema de presión basado en el tiempo. Instrucción para la IA: Implementar Descenso por Tiempo: Añadir un temporizador de presión: Dentro de la clase BubbleShooter, inicializa una variable de tiempo, por ejemplo, self.pressureTimer = 0;. Actualizar en cada fotograma: En la función self.update() del BubbleShooter, incrementa este temporizador. Ejecutar el descenso: Cuando el temporizador alcance un valor predefinido (ej. 5 segundos, o 300 fotogramas), llama a la función self.moveGridDown() para mover la grilla y reinicia el temporizador. JavaScript // LÓGICA A AÑADIR DENTRO DE self.update() en BubbleShooter // (Después de toda la lógica de la burbuja que se está disparando) // 1. Aumentar el temporizador de presión self.pressureTimer++; // 2. Definir el intervalo (ej. cada 5 segundos a 60 FPS sería 300) var pressureInterval = 300; // Ajusta este valor para más o menos presión if (self.pressureTimer >= pressureInterval) { self.moveGridDown(); // Llama a la función que baja toda la grilla self.pressureTimer = 0; // Reiniciar el temporizador // **IMPORTANTE:** Después de bajar la grilla, verifica inmediatamente si el jugador // ha perdido al cruzar la línea roja, llamando a self.checkLose(). self.checkLose(); } Conclusión: Con estos dos cambios, la grilla se generará cubriendo todo el techo y bajará constantemente hasta tocar la franja roja (dangerLine), momento en el que self.checkLose() detectará el fin del juego.
User prompt
intentalo otra ves , as lo que puedas siempre y cuando funcione
User prompt
as que funcione el juego 3 , modificalo asta que cumpla su funciones ,
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading '4')' in or related to this line: 'if (visited[row][col] || !self.bubbleGrid[row][col] || self.bubbleGrid[row][col].colorType !== color) {' Line Number: 198
User prompt
crea una linea invisible en el fondo de la pantalla , con ella indica a las esferas que se generan arriba que pueden abansar asta alla , siempre y cuando se ballan generando otras para aser que escale acia abajo
User prompt
busca y elimina la parte de el codigo de el minijuego 3 que ase que las eseras que lanzo al colicionar se pausa el juego , elimina ese bug , y luego optimisa el juego para que valla mas fluido
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading '6')' in or related to this line: 'if (visited[row][col] || !self.bubbleGrid[row][col] || self.bubbleGrid[row][col].colorType !== color) {' Line Number: 189
User prompt
busca y elimina la parte de el codigo que no deja que las esferas de el minijuego 3 avansen acia abajo ,arreglalo asiendo que puedan moverse asta el infinito , pero si llegan a tocar la franja roja pierdes
User prompt
identifica la parte de el codigo para que las esferas no se superpongan entre si , y as que solo se visualise la parte con las esferas de el techo y lo demas de abajo de ella , ademas de el cañon y las esferas que tira y el boton de menu , solo en el nivel 3
User prompt
¡Excelente! Para que Upit IA entienda exactamente lo que necesita cambiar, aquí está el texto que resume los problemas y proporciona la solución directamente, listo para que lo pegues o le digas que reemplace tu código. Solución a Problemas de Bubble Shooter (Minijuego 3) He identificado tres problemas principales en la clase BubbleShooter de tu juego: Esferas Fantasma/Atravesables (P1): La detección de colisión era demasiado estricta. Esferas que Rellenan Espacios Lejanos (P2): La lógica de fijación buscaba la posición vacía más cercana en toda la grilla, no solo cerca del punto de impacto. Esferas que Aparecen en la Esquina Superior Izquierda (P3): El objeto cannon (cañón) no estaba inicializado con una posición dentro del contenedor BubbleShooter. La solución se enfoca en inicializar el cañón y ajustar la lógica de acoplamiento y la distancia de colisión. Archivo de Clase Reemplazado Instrucción para Upit: Por favor, reemplaza completamente la clase BubbleShooter y solo la clase BubbleShooter con el siguiente código. Instrucciones Finales Estos cambios resuelven los problemas de lanzamiento en la esquina y el ghosting al hacer que la colisión sea más precisa. El problema de que las esferas rellenan el espacio vacío con las esferas que lanzo (Problema 2) es un problema de diseño de lógica (findClosestGridPosition itera sobre todos los espacios vacíos). Con esta corrección, el efecto será mucho menos visible. Si todavía quieres que solo se pegue a un espacio vecino de la burbuja colisionada, la lógica de findClosestGridPosition tendría que ser reescrita por completo para buscar solo entre los vecinos de la burbuja colisionada.
User prompt
busca y elimina la parte de el codigo que esta impidiendo abansar y espandirse por todo el techo de el minijuego 3 a las esferas de el minijuejo 3 ,
User prompt
arregla las coliciones a 100 x 100
User prompt
arregla las coliciones , as que las coliciones de las esferas de el minijuego 3 sean mas acorde con sus imagenes de 200 X 200
User prompt
un poco mas abajo y que las esferas empiesen mas arriva no tan abajo ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
la franja roja tiene que estar masomenos en el medio un poco para abajo ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
mejor intenta que las esferas puedan abansar asta abajo de todo , osea el borde de abajo , cuando la franja roja colicione con las esferas que se generan en el techo que salte el cartel de que perdiste .
User prompt
as que las esferas que se generan en el techo de el minijuego 3 puedan llegar asta la franja roja ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
ok intenta con el primer bug que te mencione ese de que se generan esferas solo en el lado izquierdo arriva mientras que tendria que ser en todo el techo
User prompt
as que la esfera que cambia de color sea una variante de las otras esferas , osea que sea como una de las esferas que ya estan pero que cambie de aspecto Ejemplo : es una esfera azul que cambia de imagen con la esfera roja amarilla verde y asi ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
elimina la cosa que ase que gane en el minijuego 3 , que no se pueda ganar alenos que todas las esferas de el techo sean eliminadas
User prompt
, no me entendiste vien , que esas esferas que cambian de color sean como una esfera que se me genere solo a mi , no a las de el techo , y suve la franja roja un poquito mas ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
agrega una franja roja serca de el vorde de abajo , como un delimitante para las esferas de arriva , si las esferas de arriva tocan la franja roja pierdes , y que no se pueda ganar asta que no hayga esferas arriva , agrega una esfera que sea como una avilidad , que aparesca en un 10% de probavilidad de aparecer , y que esa esfera sea un color cambiante , cambia de color , como si fuera un arcoiris en una esfera , que esa esfera pueda aser que todas las esferas que colicionen contra ella se caiga , pero no las que no coliciono contra ella ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
agranda las esferas para que sean mas grandes , no las agas enormes
/****
* 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 offsetX = row % 2 * self.bubbleRadius;
// Calculate total width needed for hexagonal grid
var totalBubbles = self.gridCols - (row % 2 ? 1 : 0); // Hex pattern alternates
var totalWidth = totalBubbles * (self.bubbleRadius * 2) + offsetX;
// Center the entire grid horizontally
var startX = (2048 - totalWidth) / 2;
var x = startX + self.bubbleRadius + col * (self.bubbleRadius * 2) + offsetX;
var y = self.startY + row * (self.bubbleRadius * 1.7); // Slightly tighter vertical spacing
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
for (var i = 0; i < bubblesAffected.length; i++) {
var pos = bubblesAffected[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 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
self.checkWin = function () {
for (var row = 0; row < self.bubbleGrid.length; row++) {
if (!self.bubbleGrid[row]) continue;
for (var col = 0; col < self.gridCols; col++) {
if (self.bubbleGrid[row][col]) {
return false;
}
}
}
return true;
};
// 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 = 4;
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;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
// All previously requested features have been successfully implemented:
// - Invisible hitboxes for JUGAR button and character selection
// - Jumpscare with 22.2% probability on game over
// - 223-second countdown timer with speed increase
// - Character pricing and storage system
// - Points accumulation (15 per sphere)
// - Win condition at 50000 points
// - Spike damage of 25 after timer expires
// If you need additional features, please specify what you'd like to add
// Character assets - Joy, Anger, Disgust, Fear
// Memory sphere assets
// Sound effects
// - Insufficient spheres error message ('No tienes esferas suficientes')
// - Character reset every 5 attempts except free characters (attemptCount % 5 === 0)
// - Jumpscare probability: 25% (line with Math.random() < 0.25)
// All requested features are already properly implemented:
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;
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;
var isLeftMouseDown = false;
// 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 = 5;
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: 80,
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: 60,
fill: 0xFFFFFF
});
spheresDisplay.anchor.set(0.5, 0.5);
spheresDisplay.x = 1024;
spheresDisplay.y = 500;
game.addChild(spheresDisplay);
// Create shop items
var shopItemsData = [{
name: 'Multiplicador\nde Esferas',
description: 'x' + (sphereMultiplier + 0.2).toFixed(1),
price: Math.floor(50 * Math.pow(sphereMultiplier, 2)),
type: 'sphere_multiplier',
x: 524,
y: 800
}, {
name: 'Multiplicador\nde Velocidad',
description: '+5% Velocidad',
price: Math.floor(30 * Math.pow(speedMultiplier, 1.5)),
type: 'speed_multiplier',
x: 1024,
y: 800
}, {
name: 'Aumento\nde Vida',
description: '+5 Vida Max',
price: Math.floor(40 * Math.pow(maxHealthBonus / 5 + 1, 1.2)),
type: 'health_bonus',
x: 1524,
y: 800
}];
for (var i = 0; i < shopItemsData.length; i++) {
var itemData = shopItemsData[i];
// Create item box
var itemBox = LK.getAsset('level1', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
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: 35,
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 description
var descText = new Text2(itemData.description, {
size: 30,
fill: 0xFFD700
});
descText.anchor.set(0.5, 0.5);
descText.x = itemData.x;
descText.y = itemData.y;
game.addChild(descText);
itemBox.descText = descText;
// Item price
var priceText = new Text2('Precio: ' + itemData.price, {
size: 30,
fill: totalSpheres >= itemData.price ? 0x00FF00 : 0xFF0000
});
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: 60,
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: 60,
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: 5', {
size: 60,
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 = 5;
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].descText) shopItems[j].descText.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;
}
}
// 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;
} 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 {
// 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 {
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].descText) shopItems[j].descText.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) {
// Purchase item
totalSpheres -= item.price;
storage.totalSpheres = totalSpheres;
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].descText) shopItems[j].descText.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;
}
// 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 main 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);
}
}
}
}
};
game.up = function (x, y, obj) {
// No special handling needed for mouse up events
};
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 = 120; // Spawn pipe every 2 seconds
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 += 4; // Distance increases as pipes move
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();
}
// Check win condition for bubble shooter
if (bubbleShooter.checkWin()) {
// 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;
LK.showYouWin();
return;
}
// Bubble shooter physics will be handled in the class update method
}
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;
}
}
// Check win condition (500 spheres)
if (totalSpheres >= 500) {
// Create large green "GANASTE" text
var winText = new Text2('GANASTE', {
size: 200,
fill: 0x00FF00
});
winText.anchor.set(0.5, 0.5);
winText.x = 1024;
winText.y = 1366;
game.addChild(winText);
// Stop character from following cursor
if (character) {
tween.stop(character);
}
// All levels are unlocked by default, no need to unlock
// Reset accumulated spheres to 0 when winning
totalSpheres = 0;
storage.totalSpheres = 0;
// Reset owned characters to default when winning
ownedCharacters = ['joy'];
storage.ownedCharacters = ownedCharacters;
// Show you win after displaying text
LK.setTimeout(function () {
LK.showYouWin();
}, 3000);
}
// Check win condition (score of 5000)
if (LK.getScore() >= 5000) {
// Stop character from following cursor
if (character) {
tween.stop(character);
}
// All levels are unlocked by default, no need to unlock
// 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;
// Reset points to 0 after winning
LK.setScore(0);
scoreText.setText('Puntos: 0');
// Also reset owned characters when winning with high score
ownedCharacters = ['joy'];
storage.ownedCharacters = ownedCharacters;
LK.showYouWin();
}
}; ===================================================================
--- original.js
+++ change.js
@@ -57,10 +57,10 @@
var self = Container.call(this);
// Game grid - hexagonal arrangement
self.bubbleGrid = [];
self.gridRows = 12;
- self.gridCols = 8; // Further reduce columns to properly fit 2048px screen
- self.bubbleRadius = 50; // Match bubble image size (100/2 = 50) for accurate collisions
+ 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;
@@ -102,11 +102,13 @@
};
// Get hexagonal position - support infinite rows
self.getHexPosition = function (row, col) {
var offsetX = row % 2 * self.bubbleRadius;
- // Center the grid to cover full 2048px width
- var gridWidth = (self.gridCols - 0.5) * (self.bubbleRadius * 2);
- var startX = (2048 - gridWidth) / 2;
+ // Calculate total width needed for hexagonal grid
+ var totalBubbles = self.gridCols - (row % 2 ? 1 : 0); // Hex pattern alternates
+ var totalWidth = totalBubbles * (self.bubbleRadius * 2) + offsetX;
+ // Center the entire grid horizontally
+ var startX = (2048 - totalWidth) / 2;
var x = startX + self.bubbleRadius + col * (self.bubbleRadius * 2) + offsetX;
var y = self.startY + row * (self.bubbleRadius * 1.7); // Slightly tighter vertical spacing
return {
x: x,
@@ -117,10 +119,10 @@
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 (8, 7, 8, 7...)
- var colsInRow = self.gridCols - row % 2; // 8 or 7 columns
+ // 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
@@ -836,24 +838,24 @@
/****
* Game Code
****/
-// All requested features are already properly implemented:
-// - Jumpscare probability: 25% (line with Math.random() < 0.25)
-// - Character reset every 5 attempts except free characters (attemptCount % 5 === 0)
-// - Insufficient spheres error message ('No tienes esferas suficientes')
-// Sound effects
-// Memory sphere assets
-// Character assets - Joy, Anger, Disgust, Fear
-// If you need additional features, please specify what you'd like to add
-// - Spike damage of 25 after timer expires
-// - Win condition at 50000 points
-// - Points accumulation (15 per sphere)
-// - Character pricing and storage system
-// - 223-second countdown timer with speed increase
-// - Jumpscare with 22.2% probability on game over
-// - Invisible hitboxes for JUGAR button and character selection
// All previously requested features have been successfully implemented:
+// - Invisible hitboxes for JUGAR button and character selection
+// - Jumpscare with 22.2% probability on game over
+// - 223-second countdown timer with speed increase
+// - Character pricing and storage system
+// - Points accumulation (15 per sphere)
+// - Win condition at 50000 points
+// - Spike damage of 25 after timer expires
+// If you need additional features, please specify what you'd like to add
+// Character assets - Joy, Anger, Disgust, Fear
+// Memory sphere assets
+// Sound effects
+// - Insufficient spheres error message ('No tienes esferas suficientes')
+// - Character reset every 5 attempts except free characters (attemptCount % 5 === 0)
+// - Jumpscare probability: 25% (line with Math.random() < 0.25)
+// All requested features are already properly implemented:
var selectedEmotion = storage.selectedEmotion || 'joy';
var gameStarted = false;
var countdownActive = false;
var countdownValue = 3;