Croqueta Clicker es un juego incremental (o clicker game) desarrollado con Angular 21. El objetivo es simple: generar la mayor cantidad de croquetas posible. El jugador comienza haciendo clic manualmente en una croqueta gigante y, a medida que acumula puntos (croquetas), puede comprar productores que las generan automáticamente y mejoras que aumentan la eficiencia de sus clics.
El proyecto está diseñado con una arquitectura modular basada en componentes standalone de Angular 21, desacoplando la lógica de negocio del estado de la interfaz de usuario. Utiliza características modernas de Angular como:
- Signals: Gestión de estado reactiva y eficiente con
signal(),computed(),input(),output() - Zoneless Change Detection: Mejor rendimiento eliminando Angular Zone.js
- ChangeDetectionStrategy.OnPush: En todos los componentes para optimizar renders
- Control flow nativo: Directivas
@if,@for,@switchen templates en lugar de*ngIf,*ngFor - Host bindings modernos: Uso de
hostproperty en lugar de@HostListener/@HostBinding`
- Puntos (Croquetas): Es la moneda principal del juego. Se obtienen mediante clics manuales o a través de productores automáticos.
- Experiencia (EXP) y nivel: El jugador gana EXP al realizar acciones (clics, compras). Al acumular suficiente EXP, sube de nivel, lo que desbloquea nuevas mejoras, productores y contenido.
- Productores: Son "edificios" o entidades que el jugador puede comprar para generar croquetas automáticamente (puntos por segundo). Su coste aumenta exponencialmente con cada compra.
- Mejoras (Upgrades): Aumentan la cantidad de croquetas obtenidas por cada clic manual. Suelen tener un requisito de nivel para ser desbloqueadas.
- Skins: Elementos cosméticos que cambian la apariencia de la croqueta principal. Se desbloquean al cumplir ciertos requisitos (nivel, total de croquetas, logros, etc.).
- Logros (Achievements): Metas que el jugador puede cumplir para marcar su progreso. Desbloquear logros es persistente.
- Eventos especiales: Como la "Croqueta Dorada", que aparece aleatoriamente y otorga un bonus temporal si se le hace clic.
El núcleo de la lógica del juego reside en la carpeta src/app/services/, mientras que los datos estáticos (configuraciones de productores, mejoras, etc.) se encuentran en src/app/data/.
El estado del juego se guarda en el localStorage del navegador.
OptionsServicecomo intermediario: Este servicio es el único punto de contacto directo conlocalStorage. Centraliza la lectura (getGameItem) y escritura (setGameItem), añadiendo un prefijo (croqueta-clicker_) a todas las claves para evitar colisiones.- Servicios de estado: Servicios como
PointsService,PlayerStats,SkinsService, etc., cargan su estado inicial desdeOptionsServiceen su constructor. - Guardado de datos:
- Manual: Ciertas acciones críticas, como un clic manual o una compra, disparan un guardado inmediato a través de
saveToStorage()en el servicio correspondiente. - Automático:
AutosaveServicese encarga de guardar periódicamente (cada 60 segundos) el estado de los servicios más importantes (PointsService,PlayerStats) para prevenir la pérdida de progreso. También guarda el estado justo antes de que la pestaña del navegador se cierre (beforeunload).
- Manual: Ciertas acciones críticas, como un clic manual o una compra, disparan un guardado inmediato a través de
A continuación se detalla la responsabilidad de cada servicio principal:
-
PointsService:- Responsabilidad: Gestiona la lógica económica central del juego.
- Estado que maneja: signals para
points(croquetas totales),pointsPerClick(croquetas por clic) ypointsPerSecond(croquetas por segundo). - Funcionalidad clave:
addPointsPerClick(): Añade puntos por un clic manual, aplicando multiplicadores.addPointPerSecond(): Se ejecuta a intervalos regulares para añadir los puntos generados automáticamente.upgrade...(): Métodos para actualizar los puntos por clic/segundo al comprar mejoras o productores.- Utiliza
break_infinity.jspara manejar números muy grandes. - Implementa
ChangeDetectionStrategy.OnPushpara renders optimizados - Usa
effect()para reaccionar a cambios de estado - Manejo de inyección con
inject()en lugar de constructor
-
PlayerStats:- Responsabilidad: Gestiona el progreso y las estadísticas del jugador.
- Estado que maneja:
level,currentExp,expToNext,totalClicks,timePlaying. - Funcionalidad clave:
addClick()yaddExp(): Incrementan la experiencia.checkLevelUp(): Verifica si el jugador ha subido de nivel y, si es así, actualiza el nivel y la EXP necesaria para el siguiente, notificando aLevelUpService.
-
OptionsService:- Responsabilidad: Actúa como un gestor de configuración y el principal intermediario con
localStorage. - Funcionalidad clave:
- Gestiona opciones del juego (volumen, partículas, etc.).
- Proporciona métodos
getGameItem,setGameItemyremoveGameItemcon prefijo para que otros servicios persistan su estado. - Implementa la lógica para exportar/importar la partida y reiniciar el juego.
- Responsabilidad: Actúa como un gestor de configuración y el principal intermediario con
-
AchievementsService:- Responsabilidad: Gestiona el desbloqueo y la persistencia de los logros.
- Funcionalidad clave:
unlockAchievement(id): Desbloquea un logro, lo guarda enlocalStoragey lo añade a una cola (queue$) para ser mostrado en la UI.- Maneja logros especiales como "desbloquea tu primer logro" y "desbloquea todos los logros".
-
SkinsService:- Responsabilidad: Gestiona las apariencias (skins) de la croqueta.
- Funcionalidad clave:
isSkinUnlocked(skin): Comprueba si una skin está desbloqueada basándose en requisitos (nivel, puntos, logros) que obtiene de otros servicios (PlayerStats,PointsService,AchievementsService).- Mantiene un
Setde las skins usadas para desbloquear logros relacionados.
-
AudioService:- Responsabilidad: Controla toda la reproducción de audio.
- Funcionalidad clave:
- Usa la Web Audio API para un control avanzado del sonido.
playSfx(): Reproduce efectos de sonido, cacheando los buffers para mayor eficiencia.playMusic(): Gestiona la música de fondo, permitiendo crossfading suave entre pistas.- Se suscribe a los
Observablesde volumen deOptionsServicepara ajustar las ganancias en tiempo real.
-
AutosaveService:- Responsabilidad: Orquesta el guardado automático del progreso del juego.
- Funcionalidad clave:
- Inicia un
setIntervalque llama asaveAll()cada 60 segundos. saveAll()fuerza a los servicios principales a guardar su estado actual.- Se engancha al evento
beforeunloaddel navegador para un último guardado antes de cerrar la página.
- Inicia un
-
Servicios de UI y efectos:
FloatingService: Muestra texto flotante (ej.+100).ParticlesService: Crea y gestiona efectos de partículas.ModalService: Controla qué modal (Stats, Skins, etc.) está visible.LevelUpServiceyNewsService: Gestionan colas de notificaciones para la UI.
-
ReportService:- Responsabilidad: Genera los datos para el panel de informes y estadísticas.
- Funcionalidad clave:
getGameSummary(): Resumen del jugador (nivel, croquetas, CPS, tiempo jugado).getProducersData(),getUpgradesData(),getAchievementsData(),getSkinsTableData(): Datos para tablas.getProducerDistribution(),getCpsDistribution(),getProducerROIData(),getSkinUnlockByRarityDonut(): Datos para gráficos.getEfficiencyData(): Campos calculados (clicks/min, croquetas/min, ROI, eficiencia de upgrades).getDebugInfo(): Información técnica (localStorage, idioma, versiones).
-
ReportPdfService:- Responsabilidad: Exporta el informe a PDF.
- Funcionalidad clave:
exportReport(payload): Genera un PDF con jsPDF + AutoTable incluyendo tablas, gráficos y estadísticas.- Convierte los gráficos del DOM a imagen con
html2canvasycanvg.
-
Internacionalización (i18n) con Transloco:
SmartMissingHandlerenapp.config.ts: Silencia warnings de traducción durante los primeros 3 segundos (antes de que el JSON cargue), pero loguea claves genuinamente faltantes después.- El componente
ReportusaselectTranslation()en lugar delangChanges$para asegurar querefreshData()solo se ejecuta cuando las traducciones están disponibles.
Esta carpeta contiene los "blueprints" de todos los elementos del juego, lo que facilita el balance y la adición de nuevo contenido sin tocar la lógica de los servicios.
producer.data.ts: Define cada productor, su coste base, su producción, etc.upgrade.data.ts: Define cada mejora de clic, su coste, el bono que otorga y el nivel requerido.skin.data.ts: Define las skins, su rareza y sus requisitos de desbloqueo.achievements.data.ts: Define todos los logros disponibles.news.data.ts: Contiene los mensajes de noticias.tutorial.data.ts: Define los mensajes del tutorial y sus condiciones de aparición.
- Usuario: Hace clic en el botón de compra de una mejora en la UI.
- Componente (
Upgrade.ts): Llama al métodobuyUpgrade(). - Lógica de compra:
- El componente verifica si el jugador tiene suficientes puntos llamando a
pointsService.points().gte(price). - Si es así, llama a
pointsService.substractPoints(price)para restar el coste. - Llama a
pointsService.upgradePointPerClick()para actualizar el valor de los clics. - Llama a
playerStats.addExp()para otorgar la experiencia de la compra. - Llama a
audioService.playSfx()para reproducir un sonido de confirmación. - Actualiza su estado interno a
bought = truey lo guarda usandooptionsService.setGameItem().
- El componente verifica si el jugador tiene suficientes puntos llamando a
- Reacción en cadena:
- El cambio en
pointsPerClickenPointsServicepuede hacer quePlayerStatsactualice elexpPerClick. - El aumento de
EXPenPlayerStatspuede desencadenar una subida de nivel (checkLevelUp()). - Una subida de nivel notifica a
LevelUpServicey puede desbloquear nuevas skins (SkinsService) o logros (AchievementsService). - Todos los cambios de estado persistentes son guardados en
localStoragepor el servicio correspondiente.
- El cambio en