User prompt
elimina lo siguiente : 🚀 Instrucción: Funciones y Misiones para el GlosarioI. Funciones Críticas del GlosarioLa IA debe implementar la lógica de seguimiento, estado y recompensa.1. Rastrear Progreso (self.checkMissionProgress())Instrucción: Crea una función que se llame al final de cada partida de minijuego y cuando se reclama una recompensa.Lógica: Esta función debe comparar la puntuación total o acumulada del jugador (ej., self.totalObstaclesEsquivados, self.totalEsferasRecogidas) con los requisitos de las misiones.Actualización: Si se cumple un requisito, la IA debe establecer una variable de estado (ej., missionList[id].isReadyToClaim = true).2. Reclamar Recompensa (self.claimMission(missionID))Instrucción: Esta función debe ejecutarse cuando el jugador hace clic en el botón "RECLAMAR" de una misión.Lógica:Suma la cantidad de Gemas de recompensa a la variable global self.gemas.Establece el estado final de la misión (ej., missionList[id].isCompleted = true).Deshabilita el botón "RECLAMAR" para esa misión de forma permanente.3. Actualización de la UI del GlosarioInstrucción: La interfaz visual del Glosario (las tarjetas rojas/rosas) debe mostrar tres estados claros para cada misión:Misión en Progreso: Muestra el texto "Progreso: X / Requisito".Lista para Reclamar: Muestra el texto "¡RECLAMAR!" y el botón activo.Completada: Muestra el texto "RECLAMADA" o se desvanece a un color gris.II. Misiones Adicionales (Objetivos Fijos)Aquí tienes más misiones para aumentar el desafío y las Gemas disponibles, siguiendo el formato de objetivo fijo y recompensa de Gema.A. Misiones del Minijuego 1: Cosecha de Esferas (Techo)MisiónNombreRequisito (Único)Recompensa (Gemas)M-1DRacha CortaRecoger 10 esferas doradas seguidas.45 GemasM-1EEsfera TotalRecoger 500 esferas en total (Acumulativo).120 GemasB. Misiones del Minijuego 2: Vuelo (Flappy Bird)MisiónNombreRequisito (Único)Recompensa (Gemas)M-2DVuelo InicialEsquivar 20 obstáculos en un solo intento.30 GemasM-2EPuntaje AltoAlcanzar una puntuación de 50 en un solo intento.150 GemasC. Misiones del Minijuego 3: Bubble ShooterMisiónNombreRequisito (Único)Recompensa (Gemas)M-3DEfecto DominoEliminar 10 burbujas rojas en una sola caída.70 GemasM-3EPrecisión de ColorRealizar 15 disparos exitosos seguidos (de 3 o más burbujas).110 GemasD. Misiones Globales (Acumulación)MisiónNombreRequisito (Único)Recompensa (Gemas)M-G4Comprador CompulsivoComprar un total de 5 mejoras en la Tienda (Acumulativo).100 GemasM-G5Superar 100Obtener al menos 100 puntos en cualquier minijuego.250 Gemas ↪💡 Consider importing and using the following plugins: @upit/storage.v1 ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot set properties of undefined (setting 'alpha')' in or related to this line: 'titleText.alpha = 0;' Line Number: 1805
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'storage.missionList = missionList;' Line Number: 2343
User prompt
🚀 Instrucción: Funciones y Misiones para el GlosarioI. Funciones Críticas del GlosarioLa IA debe implementar la lógica de seguimiento, estado y recompensa.1. Rastrear Progreso (self.checkMissionProgress())Instrucción: Crea una función que se llame al final de cada partida de minijuego y cuando se reclama una recompensa.Lógica: Esta función debe comparar la puntuación total o acumulada del jugador (ej., self.totalObstaclesEsquivados, self.totalEsferasRecogidas) con los requisitos de las misiones.Actualización: Si se cumple un requisito, la IA debe establecer una variable de estado (ej., missionList[id].isReadyToClaim = true).2. Reclamar Recompensa (self.claimMission(missionID))Instrucción: Esta función debe ejecutarse cuando el jugador hace clic en el botón "RECLAMAR" de una misión.Lógica:Suma la cantidad de Gemas de recompensa a la variable global self.gemas.Establece el estado final de la misión (ej., missionList[id].isCompleted = true).Deshabilita el botón "RECLAMAR" para esa misión de forma permanente.3. Actualización de la UI del GlosarioInstrucción: La interfaz visual del Glosario (las tarjetas rojas/rosas) debe mostrar tres estados claros para cada misión:Misión en Progreso: Muestra el texto "Progreso: X / Requisito".Lista para Reclamar: Muestra el texto "¡RECLAMAR!" y el botón activo.Completada: Muestra el texto "RECLAMADA" o se desvanece a un color gris.II. Misiones Adicionales (Objetivos Fijos)Aquí tienes más misiones para aumentar el desafío y las Gemas disponibles, siguiendo el formato de objetivo fijo y recompensa de Gema.A. Misiones del Minijuego 1: Cosecha de Esferas (Techo)MisiónNombreRequisito (Único)Recompensa (Gemas)M-1DRacha CortaRecoger 10 esferas doradas seguidas.45 GemasM-1EEsfera TotalRecoger 500 esferas en total (Acumulativo).120 GemasB. Misiones del Minijuego 2: Vuelo (Flappy Bird)MisiónNombreRequisito (Único)Recompensa (Gemas)M-2DVuelo InicialEsquivar 20 obstáculos en un solo intento.30 GemasM-2EPuntaje AltoAlcanzar una puntuación de 50 en un solo intento.150 GemasC. Misiones del Minijuego 3: Bubble ShooterMisiónNombreRequisito (Único)Recompensa (Gemas)M-3DEfecto DominoEliminar 10 burbujas rojas en una sola caída.70 GemasM-3EPrecisión de ColorRealizar 15 disparos exitosos seguidos (de 3 o más burbujas).110 GemasD. Misiones Globales (Acumulación)MisiónNombreRequisito (Único)Recompensa (Gemas)M-G4Comprador CompulsivoComprar un total de 5 mejoras en la Tienda (Acumulativo).100 GemasM-G5Superar 100Obtener al menos 100 puntos en cualquier minijuego.250 Gemas ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
as que el texto " tiendas de mejoras "y el texto "esferas: " solo se vean cuando este en el apartado de la tienda de mejoras , osino as que esos textos se agan invisibles para que no se vean mientras juego adentro de los minijuegos
User prompt
¡Por supuesto! Al ver la imagen de la **"TIENDA DE MEJORAS"**, parece que los cuadros de mejora están bien espaciados entre sí, pero están desplazados hacia un lado o necesitan un mejor centrado horizontal. Aquí tienes la instrucción para Upit IA para que centre perfectamente las mejoras y el contenido de la tienda en la pantalla: --- ## 📐 Instrucción: Centrado Horizontal de la Tienda de Mejoras La IA debe recalcular la posición horizontal (coordenada X) del contenedor de las mejoras para que el conjunto completo quede centrado en la pantalla. ### 1. Centrado de los Cuatro Slots **Instrucción para la IA:** > **Ajuste de la Posición X de los Slots de Mejora:** > > 1. **Determinar el Ancho Total:** Calcula el ancho total que ocupan los cuatro slots de mejora, incluyendo el espacio de margen entre ellos. > 2. **Centrado Horizontal:** Mueve el punto de inicio de la grilla de mejoras hacia la izquierda o derecha para que el centro de este ancho total coincida con el centro exacto de la pantalla. > 3. **Ejemplo (Lógica de Centrado):** Si el ancho total de los 4 slots es `W_Total` y el ancho de la pantalla es `Screen_W`, el punto de inicio de la primera mejora debe ser `(Screen_W / 2) - (W_Total / 2)`. ### 2. Centrado del Título y Contadores **Instrucción para la IA:** > **Centrar el Título y Contadores Superiores:** > > 1. Asegúrate de que el título **"TIENDA DE MEJORAS"** y el contador de **"Esferas/Gemas: XX"** se mantengan perfectamente **centrados** en la parte superior, independientemente de la posición de los cuadros de mejora. ### 3. Centrado del Contenido Interno **Instrucción para la IA:** > **Centrar el Texto Dentro de Cada Cuadro:** > > 1. Dentro de cada uno de los cuatro cuadros de mejora (los rectángulos verdes), los textos de **"Multiplicador de..."**, **"Precio: XX"** y el **Nivel/Efecto** deben estar centrados **horizontalmente** dentro de su propio cuadro verde, sin que se desborden por los lados. Con esta instrucción, el diseño de la Tienda se verá profesional y equilibrado en la pantalla.
User prompt
🚫 Instrucción: Ocultar Elementos de Tienda en Minijuegos La IA debe asegurarse de que los elementos de la Tienda solo se muestren en la pantalla de la Tienda y no se superpongan con el juego activo. 1. Control de Visibilidad por Estado Instrucción para la IA: Restricción de Visibilidad (CRÍTICO): Identifica los objetos de texto/UI de la Tienda que se están superponiendo: Título: El texto "TIENDA DE MEJORAS". Contador de Moneda: El texto "Esferas: XX". Asegura que estos elementos solo sean creados y visibles cuando el estado del juego sea 'TIENDA_ACTIVA' o 'ENTRE_PARTIDAS'. Destrucción/Ocultamiento: Cuando el juego cambia a un estado activo (ej., 'MINIJUEGO_1_ACTIVO' o 'MINIJUEGO_3_ACTIVO'), estos objetos deben ser inmediatamente: Destruidos (usando .destroy()) Ocultados (usando sprite.visible = false) Nota: Si la IA ya implementó la función de limpieza forzada (self.hideMainMenu()), debe asegurarse de que estos elementos de la Tienda sean incluidos en esa función de limpieza. 2. Excepción para Puntuación Instrucción para la IA: Mantener el Contador de Puntuación: El contador de "Puntos: X" debe permanecer visible, ya que es la métrica de puntuación actual del minijuego. Solo deben ocultarse los elementos relacionados con la compra. Esto resolverá la superposición de la interfaz y permitirá al jugador ver claramente los minijuegos.
User prompt
elimina el multiplicador de el tiempo desde el codigo asta las imagenes que lo componen para que no estorve
User prompt
tambien eliminalo desde el codigo , para que no estorve
User prompt
elimina esto ¡Tienes razón! Al mirar las imágenes de la "TIENDA DE MEJORAS", es obvio que el texto que indica el nivel o el efecto de la mejora (ej., "x1.2," "15% Velocidad," "+5 Vida Max") se está superponiendo con el nombre de la mejora o el precio. Aquí tienes la instrucción directa para Upit IA para solucionar esta superposición simplemente moviendo el texto. 📐 Instrucción: Ajuste de Posición del Texto de Nivel en la Tienda La IA debe bajar la posición vertical de los textos que indican el nivel o el efecto actual de cada mejora para que no se superpongan con el título o el precio. 1. Ajuste de la Coordenada Y (Vertical) Instrucción para la IA: Bajar el Texto de la Propiedad: Identifica las variables de texto responsables de mostrar el nivel o el efecto actual de la mejora (ej., los textos "x1.2", "15% Velocidad", o "+5 Vida Max"). Aumenta la coordenada Y (vertical) de estos objetos de texto en una cantidad suficiente (ej., moverlos +15 o +20 píxeles hacia abajo). Objetivo: El texto del nivel/efecto debe quedar justo debajo del título de la mejora (ej., "Multiplicador de Esferas") y encima del precio, sin superponerse con ninguno de los dos. 2. Verificación de Contraste Instrucción para la IA: Confirmar el Contraste: Asegúrate de que este texto, una vez reubicado, siga usando un color que sea legible contra el fondo verde oscuro (preferiblemente Blanco o Amarillo). Con este simple ajuste de la coordenada 'Y', se eliminará la superposición y la Tienda será mucho más clara.
User prompt
mejor elimina esto : ✅ Update shop item generation logic with proper rarities and limits ✅ Add time multiplier upgrade variables to storage system ✅ Add time multiplier upgrade variables and time counter display ✅ Add orange time counter display to GUI ✅ Handle time multiplier purchases in shop system ✅ Add time counter display logic during gameplay ✅ Add mouse tracking for time counter pause/resume logic ✅ Update mouse down handler to track left mouse button state ✅ Update mouse up handler to track left mouse button state ✅ Add time counter update logic and synergy with speed multiplier ✅ Hide time counter when returning to menu ⏪Revert to here TH Please fix the bug: 'Timeout.tick error: Cannot set properties of undefined (setting 'alpha')' in or related to this line: 'timeCounterText.alpha = 1;' Line Number: 2078 Aborted: User aborted prompt TH 🛠️ Instrucción: Mejoras de Tienda Permanentes y Fijas La IA debe eliminar toda la lógica de rareza (probabilidades del 10% y 25%) y la rotación. Todas las mejoras deben estar disponibles cada vez que se abre la Tienda. 1. Eliminación de Lógica de Rareza (CRÍTICO) Instrucción para la IA: Descartar la Probabilidad y la Rotación: Elimina toda la lógica que utiliza números aleatorios para determinar si el "Multiplicador de Velocidad" (10% de chance) o el "Multiplicador de Tiempo" (25% de chance) deben aparecer en la Tienda. Elimina la lógica que hace que el Multiplicador de Tiempo rote con otros slots. 2. Establecer Slots Fijos (Total de 4 Mejoras) Instrucción para la IA: Crear Cuatro Slots Permanentes: La Tienda debe tener ahora cuatro slots disponibles de forma permanente. Slot 1 (Fijo): Multiplicador de Esferas Slot 2 (Fijo): Multiplicador de Velocidad Slot 3 (Fijo): Aumento de Vida Max Slot 4 (Nuevo Fijo): Multiplicador de Tiempo (Debe ser el ítem más caro y de mejora máxima más alta). Nota: Todos los demás parámetros (el límite de 20 compras, la sinergia entre Multiplicador de Velocidad y Multiplicador de Tiempo, y la lógica del contador naranja) deben permanecer sin cambios. ✅ Remove rarity logic and rotation from shop generation, making all 4 items always available ⏪Revert to here TH Please fix the bug: 'Timeout.tick error: Cannot set properties of undefined (setting 'alpha')' in or related to this line: 'timeCounterText.alpha = 1;' Line Number: 2061 Aborted: User aborted prompt
User prompt
🛠️ Instrucción: Mejoras de Tienda Permanentes y Fijas La IA debe eliminar toda la lógica de rareza (probabilidades del 10% y 25%) y la rotación. Todas las mejoras deben estar disponibles cada vez que se abre la Tienda. 1. Eliminación de Lógica de Rareza (CRÍTICO) Instrucción para la IA: Descartar la Probabilidad y la Rotación: Elimina toda la lógica que utiliza números aleatorios para determinar si el "Multiplicador de Velocidad" (10% de chance) o el "Multiplicador de Tiempo" (25% de chance) deben aparecer en la Tienda. Elimina la lógica que hace que el Multiplicador de Tiempo rote con otros slots. 2. Establecer Slots Fijos (Total de 4 Mejoras) Instrucción para la IA: Crear Cuatro Slots Permanentes: La Tienda debe tener ahora cuatro slots disponibles de forma permanente. Slot 1 (Fijo): Multiplicador de Esferas Slot 2 (Fijo): Multiplicador de Velocidad Slot 3 (Fijo): Aumento de Vida Max Slot 4 (Nuevo Fijo): Multiplicador de Tiempo (Debe ser el ítem más caro y de mejora máxima más alta). Nota: Todos los demás parámetros (el límite de 20 compras, la sinergia entre Multiplicador de Velocidad y Multiplicador de Tiempo, y la lógica del contador naranja) deben permanecer sin cambios.
User prompt
Please fix the bug: 'Timeout.tick error: Cannot set properties of undefined (setting 'alpha')' in or related to this line: 'timeCounterText.alpha = 1;' Line Number: 2078
User prompt
Please fix the bug: 'Timeout.tick error: Cannot set properties of undefined (setting 'alpha')' in or related to this line: 'timeCounterText.alpha = 1;' Line Number: 2078
User prompt
⚙️ Mejoras de la Tienda y Habilidades Las mejoras en la Tienda ahora se compran exclusivamente con Gemas (obtenidas del Glosario de Misiones) y han sido ajustadas en su precio, rareza y efecto. Todas las mejoras tienen un límite máximo de compra de 20 veces. Mejora Tipo de Aparición Uso de la Habilidad Precio Inicial (Gemas) 1. Multiplicador de Esferas Fijo (Aparición 100%) Aumenta la cantidad de Gemas o esferas que obtienes en los minijuegos. Alto (Ej. 200) 2. Aumento de Vida Max Fijo (Aparición 100%) Aumenta la vida máxima del personaje en los minijuegos. Moderado (Ej. 91) 3. Multiplicador de Velocidad Raro (10% de Chance) Aumenta la velocidad del personaje en los minijuegos. Moderado (Ej. 111) 4. Multiplicador de Tiempo Raro (25% de Chance) Aumenta la duración total del bono de tiempo que se puede usar en los minijuegos (Inicial: 1s, Máximo: 2.3 min). Muy Caro (Ej. 500) Exportar a Hojas de cálculo 🎯 Lógica de Aparición y Sinergia Rareza y Rotación de Slots Multiplicador de Tiempo (25%): Tiene una probabilidad de 25% de aparecer en la tienda. Si aparece, reemplazará aleatoriamente uno de los tres slots fijos existentes (Esferas, Velocidad o Vida). Multiplicador de Velocidad (10%): Solo tiene un 10% de probabilidad de aparecer en el slot que le corresponde. Si no aparece, el slot se muestra como "NO DISPONIBLE". Sinergia de Habilidades (CRÍTICO) El Multiplicador de Velocidad (Mejora #3) ahora tiene un efecto directo sobre el Multiplicador de Tiempo (Mejora #4): Cada nivel que se compra del Multiplicador de Velocidad hace que el tiempo de duración del bono de la Mejora de Tiempo se consuma más lentamente. Esto significa que la habilidad de tiempo dura más a medida que inviertes en velocidad. ⏱️ Contador de Tiempo (Nueva Interfaz) Se ha añadido un nuevo contador para la Mejora de Tiempo: Contador: Un contador de tiempo acumulado de color Naranja Brillante aparecerá debajo de las esferas/gemas acumuladas, pero solo cuando el jugador está dentro de un minijuego activo. Lógica de Pausa: El tiempo de este bono se pausará automáticamente cuando el jugador deje de dar clic izquierdo y se reanudará cuando vuelva a dar clic izquierdo.
User prompt
añadele 5 segundos mas a la tienda
User prompt
amplia mas los recuadros verdes de la tienda y baja unos 25 pixeles mas abajo los textos
User prompt
vajalo mas , unos 50 pixeles mas abajo
User prompt
📐 Instrucción: Ajuste de Posición del Texto de Nivel en la Tienda La IA debe bajar la posición vertical de los textos que indican el nivel o el efecto actual de cada mejora para que no se superpongan con el título o el precio. 1. Ajuste de la Coordenada Y (Vertical) Instrucción para la IA: Bajar el Texto de la Propiedad: Identifica las variables de texto responsables de mostrar el nivel o el efecto actual de la mejora (ej., los textos "x1.2", "15% Velocidad", o "+5 Vida Max"). Aumenta la coordenada Y (vertical) de estos objetos de texto en una cantidad suficiente (ej., moverlos +15 o +20 píxeles hacia abajo). Objetivo: El texto del nivel/efecto debe quedar justo debajo del título de la mejora (ej., "Multiplicador de Esferas") y encima del precio, sin superponerse con ninguno de los dos. 2. Verificación de Contraste Instrucción para la IA: Confirmar el Contraste: Asegúrate de que este texto, una vez reubicado, siga usando un color que sea legible contra el fondo verde oscuro (preferiblemente Blanco o Amarillo). Con este simple ajuste de la coordenada 'Y', se eliminará la superposición y la Tienda será mucho más clara.
User prompt
as que las misiones solo se vean en la interfas de el menu principal de el juego , y que no aparesca en las demas interfases
User prompt
🚀 Instrucción: Separación de Obstáculos en Minijuego 2 (Flappy Bird) La IA debe ajustar la lógica de generación de obstáculos para que haya más espacio horizontal entre ellos, haciendo el juego más justo. 1. Aumento de la Distancia Horizontal entre Obstáculos (CRÍTICO) Instrucción para la IA: Incrementar self.obstacleSpacingX (o variable similar): En la lógica de generación de obstáculos del Minijuego 2 (Flappy Bird), localiza la variable que controla la distancia horizontal entre los pilares/tuberías (probablemente algo como self.obstacleSpacingX, self.gapBetweenPillars, o self.spawnInterval). Aumenta significativamente su valor. Por ejemplo, si actualmente es 200 píxeles, cámbialo a self.obstacleSpacingX = 400; o incluso 500; píxeles. Objetivo: El objetivo es que la distancia entre el borde derecho de un obstáculo y el borde izquierdo del siguiente sea mayor, dando más tiempo de reacción al jugador. 2. Ajuste de la Velocidad de Desplazamiento (Opcional, pero Sugerido) Instrucción para la IA: Reducir Ligeramente self.gameSpeed (o variable similar): Si el Minijuego 2 sigue siendo muy difícil después de separar los obstáculos, considera reducir ligeramente la velocidad a la que se mueven los obstáculos (self.gameSpeed, self.scrollSpeed). Un pequeño ajuste (ej., de 5 a 4 píxeles por fotograma) puede hacer una gran diferencia en la jugabilidad. Con estas modificaciones, el Minijuego 2 será menos frustrante y más divertido al dar al jugador un espacio de maniobra adecuado.
User prompt
optimisa el menu de misiones para que no se creshee
User prompt
¡Perfecto! La imagen que has subido del **"GLOSARIO DE MISIONES"** confirma que el texto está apiñado y se desborda en el panel oscuro. La solución es clara: debemos crear **tarjetas de misión** individuales y ordenadas dentro del panel principal. Aquí tienes la instrucción detallada para que Upit IA mejore el diseño del Glosario de Misiones, obligando a usar rectángulos separadores para cada objetivo: --- ## 🎨 Mejora de Interfaz: Estructura de Tarjetas del Glosario La IA debe usar el panel oscuro como fondo y dibujar rectángulos más pequeños y claros para cada misión individual para asegurar el orden, el contraste y evitar el desborde de texto. ### 1. El Contenedor Principal (Panel Oscuro) **Instrucción para la IA:** > **Asegurar el Contenedor y el Título:** > > 1. El rectángulo grande y oscuro (el fondo del glosario) debe ser el contenedor principal y estar **centrado** en la pantalla. > 2. El título **"GLOSARIO DE MISIONES"** debe estar correctamente centrado **encima** de este panel oscuro y tener un color que contraste fuertemente con el fondo azul claro. ### 2. Estructura de Misiones Individuales (Rectángulos Separadores) **Instrucción para la IA:** > **Crear y Posicionar Rectángulos Separadores (Tarjetas de Misión):** > > La IA debe generar una serie de **rectángulos más pequeños y claros** dentro de la zona del panel oscuro para contener los detalles de cada misión individual. > > 1. **Dibujo:** Para cada misión del glosario (M-1A, M-1B, M-2A, etc.), la IA debe usar una primitiva de dibujo (ej., **`LK.Rect`** o similar) para dibujar un rectángulo de misión individual. > 2. **Apariencia:** > * **Color:** Usa un color de **contraste** claro (ej., **Gris Claro** o **Blanco semi-transparente** con `alpha: 0.7`) para que destaque del fondo oscuro del panel. > * **Margen:** Deja un margen visible de separación (ej., 10-15 píxeles) **entre** cada rectángulo de misión. > 3. **Posicionamiento:** Los rectángulos deben colocarse **uno debajo del otro** de forma ordenada, desde la parte superior del panel oscuro hacia abajo. > * La **altura** de cada rectángulo debe ser suficiente para contener **todos los textos** de la misión (Nombre, Progreso, Recompensa). ### 3. Posicionamiento del Texto (No al Desborde) **Instrucción para la IA:** > **Asegurar que el Texto del Glosario NO se Desborde (CRÍTICO):** > > 1. **Contraste:** Los textos de las misiones deben usar un color de fuente que **contraste fuertemente** con el color del **rectángulo separador** (ej., si la tarjeta es blanca, el texto debe ser negro). > 2. **Alineación:** El texto de la misión, el progreso y la recompensa deben estar perfectamente alineados y **contenidos** dentro de los límites de su rectángulo separador individual. > 3. **Ajuste:** Si el texto de las descripciones es muy largo, la IA debe **reducir el tamaño de fuente** o usar la propiedad de **ajuste de línea** (word wrapping) para que no se extienda más allá del borde de su rectángulo contenedor. Con estas "tarjetas de misión", el Glosario estará limpio, ordenado y completamente legible.
User prompt
⚡ Resumen Rápido de Instrucciones (Prioridad Máxima) I. Arreglos de Visibilidad y Tamaño (Minijuego 4: Gusano) El gusano no se ve bien. La IA debe priorizar la visibilidad y el control. Tamaño: Haz el Gusano y las Esferas de Comida aún más grandes aumentando drásticamente self.gridSize (ej., a 120). Velocidad: Haz el Gusano más lento aumentando self.moveInterval (ej., a 15). Control: Regresa al control por teclado (W, A, S, D) y elimina la lógica del ratón. Visibilidad (CRÍTICO): Para que el Gusano se vea sobre el fondo, la IA debe implementar la solución de emergencia: La función self.renderGame() debe destruir y recrear los sprites del Gusano y la Comida en cada fotograma. II. Arreglos de la Tienda de Mejoras (UI) Legibilidad: Cambia el color del texto de Precio y los Títulos de las Mejoras a Blanco Puro (#FFFFFF) para que se lean bien sobre el fondo verde. Aumenta ligeramente el tamaño de fuente. Límite de Compra: Implementa un contador para que cada mejora individualmente solo se pueda comprar 20 veces como máximo. III. Interfaz de Misiones (Glosario) Ubicación: Crea un nuevo botón en el Menú Principal llamado "MISIONES" o "GLOSARIO DE MISIONES" que abra la nueva interfaz. Estructura: Crea un fondo semi-transparente y distinto para la interfaz de misiones (que actúa como la "caja de colisiones" visual), asegurando que los textos de las misiones sean visibles sobre él.
/****
* 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
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 - 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
****/
// 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;
// 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;
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);
// Missions button
var missionsButton = new Text2('MISIONES', {
size: 70,
fill: 0x00BFFF
});
missionsButton.anchor.set(0.5, 0.5);
missionsButton.x = 1024;
missionsButton.y = 1350;
game.addChild(missionsButton);
// Add invisible collision box to missionsButton
var missionsButtonBox = LK.getAsset('level1', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.8
});
missionsButtonBox.alpha = 0;
missionsButtonBox.x = missionsButton.x;
missionsButtonBox.y = missionsButton.y;
missionsButtonBox.textElement = missionsButton;
game.addChild(missionsButtonBox);
// 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 missions interface
function createMissions() {
gameState = 'missions';
// Clean up any existing missions elements first
for (var cleanup = game.children.length - 1; cleanup >= 0; cleanup--) {
if (game.children[cleanup].isMissionElement) {
game.children[cleanup].destroy();
}
}
// Hide menu elements
titleText.alpha = 0;
playButton.alpha = 0;
missionsButton.alpha = 0;
// Main dark container background
var missionsBg = LK.getAsset('level1', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 5.5,
scaleY: 7.5
});
missionsBg.x = 1024;
missionsBg.y = 1366;
missionsBg.tint = 0x333333;
missionsBg.alpha = 0.85;
missionsBg.isMissionElement = true;
game.addChild(missionsBg);
// Missions title
var missionsTitle = new Text2('GLOSARIO DE MISIONES', {
size: 85,
fill: 0xFFFFFF
});
missionsTitle.anchor.set(0.5, 0.5);
missionsTitle.x = 1024;
missionsTitle.y = 350;
missionsTitle.isMissionElement = true;
game.addChild(missionsTitle);
// Mission data for individual cards - reduced content to prevent overflow
var missionCards = [{
title: 'MINIJUEGO 1',
description: 'Esquiva recuerdos corruptos\ny recolecta los dorados.',
y: 600
}, {
title: 'MINIJUEGO 2',
description: 'Vuela evitando obstáculos.\nToca para volar.',
y: 800
}, {
title: 'MINIJUEGO 3',
description: 'Dispara burbujas del mismo color\npara eliminarlas.',
y: 1000
}, {
title: 'MINIJUEGO 4',
description: 'Controla la serpiente.\nCome manzanas para crecer.',
y: 1200
}];
// Create individual mission cards
for (var i = 0; i < missionCards.length; i++) {
var cardData = missionCards[i];
// Create card background rectangle
var cardBg = LK.getAsset('level1', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.8,
scaleY: 1.1
});
cardBg.x = 1024;
cardBg.y = cardData.y;
cardBg.tint = 0xEEEEEE;
cardBg.alpha = 0.92;
cardBg.isMissionElement = true;
game.addChild(cardBg);
// Mission title text
var cardTitle = new Text2(cardData.title, {
size: 48,
fill: 0x000000
});
cardTitle.anchor.set(0.5, 0.5);
cardTitle.x = 1024;
cardTitle.y = cardData.y - 45;
cardTitle.isMissionElement = true;
game.addChild(cardTitle);
// Mission description text
var cardDesc = new Text2(cardData.description, {
size: 36,
fill: 0x444444
});
cardDesc.anchor.set(0.5, 0.5);
cardDesc.x = 1024;
cardDesc.y = cardData.y + 20;
cardDesc.isMissionElement = true;
game.addChild(cardDesc);
}
// Back button
var backButton = new Text2('VOLVER', {
size: 70,
fill: 0x00FF00
});
backButton.anchor.set(0.5, 0.5);
backButton.x = 1024;
backButton.y = 1500;
backButton.isMissionsBackBtn = true;
backButton.isMissionElement = true;
game.addChild(backButton);
}
// 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);
// 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: 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 description
var descText = new Text2(itemData.description, {
size: 50,
fill: 0xFFFFFF
});
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: 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: 5', {
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 = 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 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();
}
// Check if MISSIONS button was clicked
var missionsButtonX = missionsButton.x - missionsButton.width / 2;
var missionsButtonY = missionsButton.y - missionsButton.height / 2;
var missionsButtonWidth = missionsButton.width;
var missionsButtonHeight = missionsButton.height;
if (x >= missionsButtonX && x <= missionsButtonX + missionsButtonWidth && y >= missionsButtonY && y <= missionsButtonY + missionsButtonHeight) {
createMissions();
}
} else if (gameState === 'missions') {
// Check for back button click
var backButtonX = 1024 - 150;
var backButtonY = 1500 - 50;
var backButtonWidth = 300;
var backButtonHeight = 100;
if (x >= backButtonX && x <= backButtonX + backButtonWidth && y >= backButtonY && y <= backButtonY + backButtonHeight) {
// Clear missions interface using the mission element flag
for (var k = game.children.length - 1; k >= 0; k--) {
if (game.children[k].isMissionElement) {
game.children[k].destroy();
}
}
// Show menu elements
titleText.alpha = 1;
playButton.alpha = 1;
missionsButton.alpha = 1;
gameState = 'menu';
}
} 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) {
// 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].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;
}
// 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 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);
}
}
} 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) {
// 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 = 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
}; ===================================================================
--- original.js
+++ change.js
@@ -801,9 +801,9 @@
});
pipeGraphics.tint = 0x4CAF50;
pipeGraphics.width = pipeWidth;
pipeGraphics.height = isTop ? gapY - pipeGap / 2 : 2732 - (gapY + pipeGap / 2);
- self.speed = 4;
+ self.speed = 3;
self.isTop = isTop;
self.scored = false;
self.lastX = undefined;
self.update = function () {
@@ -1083,24 +1083,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;
@@ -2473,9 +2473,9 @@
spawnTimer++;
if (currentLevel === 2) {
// Level 2: Flappy Bird mechanics - completely separate from other levels
// Spawn pipes
- var pipeSpawnRate = 120; // Spawn pipe every 2 seconds
+ 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);
@@ -2522,9 +2522,9 @@
LK.showGameOver();
return;
}
// Update distance
- distanceTraveled += 4; // Distance increases as pipes move
+ 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--) {