Skip to content

Latest commit

 

History

History
161 lines (130 loc) · 17.1 KB

File metadata and controls

161 lines (130 loc) · 17.1 KB

Roadmap

Trabajo planificado por fases. Cada fase es funcional por sí sola antes de empezar la siguiente.


Fase 1 — Motor de juego ✅

Dart puro, sin dependencias de Flutter. Totalmente testeable en aislamiento.

  • Modelos inmutables: CardModel, PlayerModel, DeckModel, TurnModel, GameState
  • CardType con todas las cartas oficiales (base + gatos)
  • DeckBuilder — construcción y reparto según número de jugadores
  • DeckManager — robar, descartar, insertar en posición, peek top N
  • GameRules — validación de acciones antes de aplicarlas
  • CardRules — jugabilidad por tipo, par/trío de gatos, Nope, Defuse
  • NopeRules — cadena Nope/Nope-a-Nope con contador impar/par
  • TurnRules — rotación de turno, Attack chains
  • WinCondition — detección de ganador
  • ActionProcessor — aplicación de efectos: Attack, Skip, Favor, Shuffle, See the Future, Cat Pair/Trio, Defuse, Nope
  • GameEngine — fachada pública (startGame / apply)
  • GameEventBus — stream broadcast de eventos para UI y red
  • Tests unitarios: CardModel, DeckManager, CardRules

Fase 2 — UI base y navegación ✅

  • Rutas completas en app_router.dart (lobby, game, game-over, settings)
  • SplashScreen — logo animado con Lottie
  • HomeScreen — menú principal con botones animados
  • SettingsScreen — volumen, nombre de jugador, preferencias
  • Sistema de tema oscuro consolidado
  • Fuente personalizada integrada (ExplodingFont)
  • Tests de widgets para pantallas principales — HomeScreen (render + navegación) y SettingsScreen (carga de prefs, guardado de nombre, toggle de sonido)

Fase 3 — Lobby local WiFi ✅

  • MdnsDiscoverer — descubrimiento de salas (al principio via beacons UDP broadcast propios; migrado a mDNS/DNS-SD real con nsd en Fase 6, ver más abajo)
  • WsServer — servidor WebSocket en el host (AppConstants.localGamePort)
  • LobbyRoom / LobbyPlayer con estados vía LobbyStatus
  • LobbyRepository — createRoom / joinRoom / leaveRoom / setReady / startGame (sin UseCases separados; coordinados directamente en el repositorio)
  • LobbyScreen — creación de sala, descubrimiento y unión, lista de jugadores conectados con estado ready, botón de inicio
  • Tests del lobby (modelos, repositorio, providers) — 34 tests nuevos, 51 totales

Fase 4 — Pantalla de juego completa ✅

  • Prework de engine: ActionProcessor.resolveNopeWindow() / GameEngine.resolveNopeWindow() — difiere y resuelve los efectos de Favor, Cat Pair/Trío y Shuffle según si la cadena de Nope quedó cancelada; fix de la duplicación de bomba en Defuse (GameState.pendingBomb)
  • CardAssetResolver / CardVisuals — resuelve el asset real de una carta desde el AssetManifest si ya existe, o cae a un placeholder (color + icono + nombre) por CardType; permite ir soltando arte final carta por carta sin tocar widgets
  • GameStateProvider — implementado como GameNotifier / gameProvider (Notifier<GameSessionState>, no StateNotifier, siguiendo el patrón ya usado por lobbyProvider), con IGameGateway/LocalGameGateway de por medio para poder enchufar un gateway remoto en Fase 5 sin tocar la UI
  • GameScreen — conectado a gameProvider/lobbyProvider; layout adaptativo por reflow (Expanded + scroll interno, sin dos árboles portrait/landscape duplicados); solo el host arranca el GameEngine hoy, los no-host ven un placeholder de "esperando Fase 5"
  • PlayerHandWidget — fan de cartas, selección por tap (se descartó drag & drop: más simple y necesario igual para pares/tríos de gato)
  • CardWidget — flip animation, glow en cartas jugables; Fase 6 añadió una animación de entrada (fade + slide) para la carta recién robada (justDrawn), independiente del glow de jugable
  • DeckWidget — contador de cartas; animación de mezcla y pulso de robo resueltos en Fase 6 (GameTableView suscrito a Stream<GameEvent>, ver más abajo)
  • DiscardPileWidget — pila de descarte con última carta visible; Fase 6 añadió una transición (AnimatedSwitcher) al cambiar la carta de arriba
  • PlayersHudWidget — avatares de jugadores con contador de cartas; Fase 6 añadió un resaltado del turno actual (anillo + flecha) con transición animada al cambiar de jugador
  • NopeWindowOverlay — temporizador visual (barra de progreso client-side, sin timestamp en GameState) y botón reactivo, habilitado solo si el jugador local tiene un Nope en mano y hay una acción pendiente
  • InsertBombOverlay — slider para elegir en qué posición del mazo reinsertar la Exploding Kitten robada, entre "arriba del todo" y "abajo del todo"; solo se muestra al jugador que la robó
  • SeeTheFutureOverlay — visualización de top 3 cartas, visibilidad derivada de GameState.seeTheFutureCards, descartado como estado local de UI
  • FavorTargetOverlay — selector de objetivo para Favor, pares y tríos de gato; el trío quedó diferido en su momento (necesita elegir una carta concreta de la mano rival, que el actor no puede ver) — resuelto en Fase 6 con CardChoiceOverlay boca abajo (ChooseCardAction, elige el actor a ciegas por posición)
  • ExplosionOverlay — animación de eliminación (placeholder con Flutter puro, escala con rebote; se reemplazará por el Lottie real de AssetPaths.animExplosion cuando exista ese asset); se detecta por diff de GameState (un jugador que estaba vivo deja de estarlo), se cierra sola sin acción del jugador
  • GameOverScreen — ganador, ranking en orden real de eliminación (fix de WinCondition/nuevo GameState.eliminationOrder: antes seguía el orden de la lista de jugadores, no el cronológico) y botón de revancha, solo para el host (mismo límite que GameScreen hoy); revancha re-arranca el mismo GameEngine/bus con los jugadores de la sala actual; Fase 6 añadió una entrada escalonada (flutter_animate) para el ganador, el ranking y los botones
  • Integración de audioplayers (efectos y música de fondo) — IAudioService/AudioService (interfaz + impl, testeable con fake), GameSoundController reproduce el efecto de cada GameEvent del motor mientras dura la partida, GameScreen/GameOverScreen reproducen music_ingame.mp3/music_gameover.mp3 en loop. De paso se corrigieron los nombres de archivo en AssetPaths (no coincidían con los reales en assets/sounds/). Alcance de esta pasada: solo pantallas de partida; music_menu.mp3 para Home/Splash/Lobby/Settings quedó pendiente (no era parte de "pantalla de juego completa") — resuelto en Fase 6 con MenuMusicMixin
  • Integración de flutter_animate en cartas y transiciones — CardWidget hace un "pop" de escala al volverse jugable, y los 5 overlays (SeeTheFuture/FavorTarget/NopeWindow/InsertBomb/Explosion) tienen fade-in de entrada, mismo estilo .animate() que ya usaban Home/Splash
  • Tests de providers y casos de uso — GameNotifier: un test por método (playCard/playFavor/playCatPair/playCatTrio/playNope/defuse) verificando el TurnAction concreto que dispatchea, más el nuevo getter events; 118 tests totales pasando

Fase 5 — Red y reconexión ✅

  • WebSocketServer — host recibe acciones, aplica al engine, retransmite estado (gameNetworkBridgeProvider conecta WsServer.actionMessages con GameNotifier.applyAction, y GameNotifier.rawStates/events de vuelta con WsServer.broadcast)
  • WebSocketClient — clientes envían acciones, reciben GameState (RemoteGameNotifier, mismo GameSessionState que produce GameNotifier para el host)
  • GameStateSerializerGameState ↔ JSON para transmisión (toJson/fromJson manuales en todos los modelos del motor, mismo estilo que los del lobby)
  • EventSerializerGameEvent ↔ JSON (necesario porque los no-host no tienen motor local; su único origen de sonidos/animaciones es lo que el host reenvía)
  • ReconnectionManager — grace period + reconexión con back-off exponencial (ReconnectionManager en el host para el grace period de 60s; WsClient reconecta solo con back-off 1s→16s tras una caída no solicitada)
  • Manejo de PlayerStatus.disconnected en UI (PlayersHudWidget muestra "Reconectando…" + icono de wifi apagado)
  • Tests de serialización y reconexión (round-trip por modelo, integración real servidor+cliente en loopback para el puente y la reconexión)

Nota: se decidió no forzar un RemoteGameGateway dentro de IGameGateway (hubiera obligado a convertir apply()/startGame() a streams y reescribir los tests ya existentes de GameNotifier) — RemoteGameNotifier es una clase separada que refleja el mismo GameSessionState, ver docs/VERIFICATION_LOG.md para el detalle de la decisión y la verificación manual.


Fase 6 — Futuro 🗓

UI/UX

  • Diseño responsivo: GameTableView tiene árboles diferenciados por orientación (context.isLandscape) en vez de un único Column/Row con reflow; ancho de carta de mano y separación mazo/descarte escalan además por tamaño de pantalla (LayoutConstants, context.isTablet contra el lado corto)
  • Arrastrar cartas (drag & drop) en PlayerHandWidget, además de la selección por tap: soltar una carta jugable de inmediato (Skip/Attack/Shuffle/See the Future) sobre el DragTarget de mazo/descarte la juega directo, sin pasar por el botón "Jugar"; soltar cualquier otra carta (o una jugable fuera del DragTarget) simplemente la selecciona, igual que un tap — las combinaciones que necesitan objetivo (Favor, par/trío de gatos) siguen su flujo de selección + overlay sin cambios
  • Soporte multi-idioma español/inglés siguiendo el idioma del sistema (issue #45) — flutter_localizations + intl + flutter gen-l10n nativo (l10n.yaml, sin locale fijo en MaterialApp.router). Cubre todas las pantallas/widgets de presentation/; quedan afuera a propósito los mensajes de error de lib/network/websocket_server.dart (sin BuildContext disponible en esa capa) y el nombre de jugador por defecto en AppSettings (capa domain/) — ver docs/ARCHITECTURE.md, sección "Localización"

Mejoras técnicas pendientes

  • Migrar MdnsAdvertiser / MdnsDiscoverer de UDP broadcast a mDNS/Bonjour real (nsd) — nsd es enteramente nativo (Bonjour en Apple, NsdManager en Android); los tests mockean NsdPlatformInterface para la lógica propia, y el registro/descubrimiento real se verificó a mano en dispositivos antes de mergear (ver docs/VERIFICATION_LOG.md)
  • WifiManager.MulticastLock vía platform channel en Android 10+ — resuelto como efecto colateral de la migración anterior: nsd_android ya lo adquiere internamente (usa el permiso CHANGE_WIFI_MULTICAST_STATE que ya estaba declarado), no hace falta un platform channel propio
  • Persistir playerId con shared_preferences para reconexión tras crash
  • Reproducir AssetPaths.musicMenu en Home/Splash/Lobby/Settings (MenuMusicMixin; antes solo GameScreen/GameOverScreen tenían música vía AudioService)
  • Validar hostAddress de un beacon contra la IP real del remitente (MdnsDiscoverer), en vez de confiar ciegamente en el valor autoreportado — superado por la migración a nsd de arriba: la dirección ya viene resuelta por el propio protocolo mDNS (Service.addresses), no de un campo autoreportado, así que el caso de spoofing que esto cerraba ya no aplica con la nueva implementación
  • Separar el manejo mDNS de host/cliente de LobbyRepository en clases propias (HostBeaconSync/ClientRoomDiscovery)
  • GameTableView puede suscribirse al Stream<GameEvent> (mismo que ya consume GameSoundController) para animaciones que un diff de GameState no puede detectar por sí solo (mezclar el mazo, distinguir un robo propio de una carta ganada por Favor/pareja/trío) — ver Fase 4 (DeckWidget/CardWidget) y CHANGELOG.md para el detalle

Bots / modo offline (issue #47)

  • Motor de bots heurístico con proyección (OSLA), configurable en dificultad — un solo motor paramétrico (BotConfig/BotWeights: pesos, determinizaciones, temperatura), no dos estrategias fijas separadas como sugería el ítem original ("básica aleatoria" + "avanzada heurística"): la dificultad sale de variar la configuración sobre el mismo núcleo. Ver docs/ARCHITECTURE.md, sección "Motor de bots", para el diseño completo
  • Partida local (pass-and-play) o LAN contra 1-4 bots — el host los agrega desde LobbyScreen antes de arrancar
  • Bots en modo online (backend Go, cards_game_service) — issue futuro y separado, sin dependencias pendientes; queda preparado (features/config en tipos serializables) pero no implementado, ver docs/ARCHITECTURE.md

Autenticación (Fase 7)

  • Mitad cliente de la identidad persistente de cards_game_service (backend hermano, repo separado): SupabaseAuthService + authServiceProvider/authSessionProvider (feature auth/, sign-in anónimo en la primera lectura), JoinRoomMessage.authToken opcional, WsClient lo manda en el join inicial y lo reenvía en cada reconexión automática, y playerIdProvider prefiere el playerId de la sesión sobre el UUID de invitado — ver docs/ARCHITECTURE.md, sección "Autenticación con Supabase", para el diseño completo
  • Conectar de verdad a cards_game_service desplegado por Internet — OnlineLobbyRepository (nueva implementación de ILobbyRepository), WsClient.connectToUri, OnlineRoomsClient (POST /rooms) y un selector explícito LAN/Online en LobbyScreen. Verificado a mano contra el backend Go real antes de mergear (ver docs/VERIFICATION_LOG.md, sección "Fase 7 — Modo online del lado cliente")
  • Soporte wss:// (TLS) en WsClientOnlineConfig.wsUri() mapea el esquema https/http del ONLINE_SERVER_URL a wss/ws; WsClient.connectToUri acepta cualquier esquema tal cual se lo pase IOWebSocketChannel
  • Vincular una cuenta anónima a una cuenta real por correo/contraseña (issue #27) — SupabaseAuthService.signUpAndLinkAnonymous (updateUser sobre la sesión anónima activa, no linkIdentity) y signInWithPassword, pantallas AccountScreen/SignUpScreen/LoginScreen accesibles desde Ajustes. Login con Google sigue pendiente, issue aparte

Modo online

  • Backend de salas (WebSocket server desplegado) — cards_game_service (repo separado), ver Autenticación (Fase 7) arriba para la mitad cliente
  • Soporte cliente para tokens de sesión emitidos por el servidor (JoinRoomMessage.token, SessionTokenMessage en WsClient) — reenviados en cada reconexión, distinto del authToken de Fase 7 (ese identifica al jugador vía Supabase; este prueba ante el backend que la reconexión es la misma sesión de red ya conocida). WsServer (host LAN) no lo implementa a propósito, así que el juego local por WiFi no se ve afectado
  • Persistir _sessionToken (hoy solo en memoria en WsClient) igual que ya se hace con playerId vía shared_preferences — si no, un crash/reinicio de la app pierde el token y el backend online rechazaría el reconnect aunque el playerId sí sobreviva
  • Sistema de cuentas / nicknames persistentes — el backend ya expone GET /players/{id} y PATCH /players/{id}/nickname, sin consumir todavía desde el cliente
  • Matchmaking por código de sala — crear sala online muestra el código; unirse online lo pide por diálogo
  • Jugar de verdad una partida online (issue #35, Stage A): el host dejó de correr GameEngine local en modo online — ahora refleja el servidor autoritativo de cards_game_service igual que cualquier no-host, con un codec nuevo (online_wire_codec.dart) que traduce el View redactado del servidor (manos rivales ocultas, CardType/TurnPhase en snake_case) al GameState de Dart en las dos direcciones
  • Trío de gatos a ciegas en modo online (issue #35, Stage B) — View.PlayerView.HiddenHandIds expone el id de carta (sin type) de la mano rival, solo mientras el viewer tiene un trío pendiente contra ella; ver cards_game_service#9
  • Revancha en modo online (issue #35, Stage C) — onStartGame acepta la sala en phaseFinished, no solo phaseWaiting; ver cards_game_service#10
  • Ranking global — el backend ya expone GET /leaderboard, sin consumir todavía desde el cliente

Expansiones

  • Imploding Kittens (6 jugadores, cartas nuevas)
  • Streaking Kittens
  • Barking Kittens (2 jugadores cooperativo)

Publicación

  • Firma y build de release para Android (Google Play)
  • Firma y build de release para iOS (App Store)
  • Assets gráficos originales completos
  • Onboarding / tutorial interactivo

Convención de commits

feat(scope):   nueva funcionalidad
fix(scope):    corrección de bug
test(scope):   tests añadidos o corregidos
refactor:      sin cambio de comportamiento externo
chore:         dependencias, configuración
ci:            pipeline y GitHub Actions
docs:          documentación

Scopes principales: core · engine · features · network · assets · ci