Cuando empezamos a hablar de solvers como infraestructura de Blupoli podía sonar a que acabaríamos construyendo una especie de algoritmo universal. La realidad ha sido más interesante. Cuanto más amplio se vuelve el catálogo, más evidente resulta que la representación correcta de cada puzzle importa tanto como la búsqueda.

Dominosa y Stitches pueden describirse como problemas de emparejamiento, pero sus restricciones no son idénticas. Slant necesita vigilar ciclos mientras coloca diagonales. Aquarium impone una geometría de niveles de agua. Str8ts combina unicidad de filas y columnas con secuencias dentro de compartimentos. Kropki convierte cada frontera entre celdas en una relación.

La parte compartida no es el algoritmo

Lo que sí se repite es el contrato. Un motor candidato debe poder responder preguntas concretas: ¿este estado cumple las reglas?, ¿existe al menos una solución?, ¿existe exactamente una cuando el juego lo exige?, ¿podemos reproducir el caso?, ¿la dificultad que anunciamos tiene alguna relación con el trabajo lógico?

Ese contrato permite que dos implementaciones internas completamente distintas se integren en la misma plataforma y pasen por gates similares.

Dominosa: cuando el tablero se convierte en exact cover

En Dominosa cada casilla debe pertenecer a un dominó y cada pareja de valores permitida debe utilizarse exactamente una vez. Visto así, el puzzle deja de ser una cuadrícula decorada y se convierte en una selección de piezas candidatas que deben cubrir simultáneamente posiciones y tipos de dominó.

Esta formulación encaja de manera natural con exact cover. Cada colocación posible cubre un conjunto de requisitos; resolver significa elegir un subconjunto compatible que cubra todos exactamente una vez. La representación reduce mucha lógica especial a un problema combinatorio más limpio.

Stitches: emparejar sin confundirlo con Dominosa

Stitches también conecta celdas o regiones, pero la semántica del enlace es diferente. El solver debe respetar qué regiones pueden conectarse, cuántas puntadas necesita cada una y qué pares siguen disponibles.

El aprendizaje aquí fue evitar una abstracción prematura. Compartir utilidades de búsqueda o conteo puede ser útil; forzar ambos juegos dentro del mismo modelo porque «los dos emparejan cosas» habría hecho el código menos claro, no más.

Slant: una restricción global escondida entre diagonales locales

Slant parece un puzzle de decisiones binarias: cada celda contiene / o \. Las pistas numéricas en los vértices imponen cantidades locales. El problema es que una configuración puede satisfacer todas esas cuentas y aun así ser inválida si las diagonales forman un ciclo.

Eso obliga al solver a combinar dos escalas. Las pistas permiten podar decisiones locales; una estructura de conectividad debe impedir que aparezcan bucles. Es un buen ejemplo de por qué validar solo el aspecto más visible de las reglas produce falsos positivos.

Aquarium: el agua no se decide celda por celda

Aquarium utiliza pistas de filas y columnas, pero las regiones que representan acuarios obedecen una propiedad física estilizada: el agua queda nivelada. Si una celda de un acuario está llena a cierta altura, las celdas del mismo acuario situadas por debajo también deben estarlo.

Tratar cada celda como una variable independiente desperdicia estructura. Modelar niveles posibles por región reduce estados imposibles desde el principio y hace que el solver piense en la misma unidad conceptual que el jugador.

Str8ts: las casillas blancas forman intervalos

Str8ts hereda parte del vocabulario de los puzzles latinos —sin repetir números por fila o columna—, pero su restricción decisiva vive dentro de cada compartimento blanco: los valores deben formar una secuencia consecutiva, aunque puedan aparecer en cualquier orden.

Eso permite razonar con intervalos. Si un compartimento de tres casillas ya contiene un 4 y los candidatos restantes solo permiten 2, 3, 5 o 6, no todas las combinaciones tienen sentido: necesitamos algún conjunto de tres valores consecutivos que incluya el 4.

Kropki: las fronteras también son variables de información

En Kropki, puntos blancos y negros relacionan vecinos, pero la ausencia de punto también restringe cuando usamos la regla negativa completa. El solver no puede mirar únicamente el contenido de las celdas; tiene que interpretar cada frontera ortogonal como una relación explícita.

La heurística de elegir primero la celda con menos candidatos funciona bien aquí porque cada número colocado activa restricciones en fila, columna y vecinos. Pero la eficacia procede del modelo de candidatos, no de que el backtracking tenga algo especial por sí mismo.

Solución única: el segundo resultado importa más que el primero

En muchos de estos juegos el solver se usa durante generación. Encontrar una solución significa que el candidato no está roto. Encontrar una segunda significa que todavía no está terminado.

Por eso varios contadores se detienen en dos. No necesitamos enumerar todo el espacio; solo distinguir cero, una o más de una solución. Esta pequeña decisión transforma al solver en una herramienta práctica para el build y para la generación en navegador.

Los motores nativos también mejoran el producto alrededor del tablero

Sustituir una plantilla fija por un motor verificado no solo añade niveles. Permite tener semillas reproducibles, cuatro dificultades coherentes, botón de nueva partida, estadísticas comparables y onboarding que puede trabajar sobre estados reales.

Es una de las razones por las que estamos priorizando profundidad antes que aumentar el catálogo sin control. Un juego deja de ser una demo cuando puede producir suficientes partidas fiables para sostener una sesión real.

No buscamos un framework de solvers por deporte

Hay una tentación muy de ingeniería: después de implementar varios algoritmos, extraer inmediatamente un framework capaz de representarlos todos. Estamos intentando resistirla. Compartimos contratos, instrumentación y piezas que ya han demostrado repetirse; dejamos que la lógica específica siga siendo específica.

Una abstracción vale la pena cuando elimina repetición sin borrar información importante. En puzzles, la información importante suele ser precisamente la forma particular de la restricción.

La diversidad del catálogo está refinando la arquitectura

Aquarium, Str8ts, Dominosa, Kropki, Stitches y Slant llegaron casi seguidos, y esa concentración ha funcionado como una prueba de estrés. Hemos tenido que alternar entre grafos, matching, exact cover, intervalos y relaciones locales sin perder el contrato de producto.

La conclusión no es que tengamos seis trucos algorítmicos nuevos. Es que Blupoli empieza a tener una forma consistente de incorporar algoritmos distintos, demostrar que hacen lo que prometen y rodearlos de una experiencia común. Esa diferencia es mucho más importante para los próximos 75 juegos que cualquier solver aislado.

Pipeline de verificación desde el modelado hasta el conteo de soluciones
La parte compartida no es el algoritmo: es el contrato de evidencia antes de publicar.

Modelar bien suele ahorrar más que optimizar tarde

Antes de discutir heurísticas conviene preguntar qué representa una variable. En Aquarium puede ser un nivel de agua por región; en Dominosa, una colocación; en Slant, una diagonal y sus conexiones. Elegir la unidad correcta elimina estados imposibles antes de la búsqueda. La primera optimización suele ser semántica.

La propagación debería hacer todo lo posible antes de ramificar

Backtracking no significa adivinar pronto. Un motor práctico aplica consecuencias hasta que no puede avanzar más. Candidatos, niveles, emparejamientos y ciclos pueden reducir el espacio enormemente. La ramificación empieza cuando la deducción se agota. Esta disciplina mejora rendimiento y produce señales útiles de dificultad.

Encontrar una solución no demuestra que el puzzle esté listo

Cuando el producto promete unicidad, el primer resultado demuestra que el candidato es resoluble; el segundo que no es publicable. Por eso el contador continúa y se detiene al llegar a dos: cero imposible, uno único, dos “más de uno”. Esta diferencia se desarrolla también en Generar no es resolver.

Contar hasta dos convierte el solver en infraestructura

El algoritmo deja de ser un botón de mostrar respuesta y se convierte en un gate usable en generación, CI y herramientas internas. Responde si el candidato cumple el contrato sin enumerar información innecesaria.

Las seeds reproducibles son parte del contrato

Cuando una partida falla, “genera otra” no sirve. Una seed, ID o estado serializado permite reconstruir el caso. También hace posibles benchmarks sobre una muestra estable.

Serializar protege contra la evolución del generador

Una seed puede producir algo distinto si cambia la generación. Guardar el estado esencial conserva regresiones, facilita diagnósticos y mantiene el puzzle comprensible fuera del DOM.

Generador y solver deberían desconfiar el uno del otro

Si el mismo código crea y certifica una partida, un error conceptual puede repetirse en ambas fases. Invariantes independientes, casos manuales, contadores separados o property tests aportan una segunda perspectiva.

Los tests de propiedades complementan las fixtures

Los casos fijos conservan bugs históricos. Property-based testing explora muchos estados y comprueba invariantes: toda solución respeta reglas, toda partida declarada única tiene una solución y serializar conserva significado. Un fallo descubierto puede convertirse en fixture permanente.

Metamorphic testing añade otra forma de buscar inconsistencias

Renombrar símbolos consistentemente, aplicar una simetría permitida o serializar y cargar no debería invalidar una solución. Estas relaciones ejercitan el motor sin necesitar una salida exacta escrita a mano para cada entrada.

Los casos adversariales merecen un lugar permanente

Una suite necesita estados casi válidos, contradicciones tardías, soluciones múltiples y ejemplos que fuerzan la regla global más cara. Cada bug real puede convertirse en una prueba que documenta una frontera del motor.

La dificultad necesita señales propias de cada familia

No existe una fórmula universal que iguale un difícil de Aquarium con uno de Kropki. Cada motor observa profundidad, candidatos, técnicas, densidad o cadenas. Blupoli puede compartir etiquetas mientras cada juego las calibra con evidencia apropiada.

Difícil no debería significar simplemente menos pistas

Eliminar información puede aumentar el reto, pero también crear ambigüedad o una deducción obvia. Un solver instrumentado ofrece propagación, bifurcaciones y profundidad: una base mejor que contar huecos.

El rendimiento necesita casos reproducibles

Una optimización puede mejorar un tablero y degradar otro. Seeds estables, tamaños representativos y casos patológicos permiten comparar percentiles y peores casos. En generación interactiva, una congelación larga pesa más que pequeñas mejoras medias.

La memoria forma parte del presupuesto

Un solver puede podar bien y consumir demasiado si copia estructuras grandes en cada rama. Rollback, representaciones compactas y mutabilidad controlada pueden ser tan importantes como el orden de búsqueda.

Los solvers largos necesitan cancelación

Una búsqueda debería detenerse si el jugador cambia de dificultad, inicia otra partida o abandona la pantalla. Un resultado tardío desperdicia recursos y puede competir con estado más reciente.

Los Web Workers pueden proteger la interfaz

Mover cálculo fuera del hilo principal mantiene la UI responsiva, pero introduce serialización, cancelación y control de respuestas obsoletas. La concurrencia merece medirse, no aplicarse por reflejo.

La caché de estados exige definir equivalencia

Memoizar puede ahorrar trabajo, pero la clave debe incluir variante, tamaño, reglas opcionales y estado. Solo podemos cachear con seguridad cuando sabemos qué hace equivalentes dos estados.

El solver no debería saber cómo se dibuja el tablero

La lógica no debería depender de CSS, DOM o animaciones. El solver necesita un modelo del puzzle. Esta frontera es central en la arquitectura de motores de Blupoli Puzzles.

La UI tampoco debería reimplementar las reglas

Si el render decide por su cuenta si una acción es válida, aparecen dos fuentes de verdad. La interfaz debe preguntar al dominio o representar estado ya validado.

Validator y solver deben compartir qué significa terminado

Una fuente clásica de bugs es que la UI declare victoria mientras el solver aplicaría otra regla. Completitud, validez y conteo necesitan una definición de dominio común.

Resolver y explicar no son la misma tarea

Un backtracking puede hallar una respuesta mediante una rama que sería una pista pésima. Un sistema de hints necesita elegir una deducción comprensible, justificarla y revelar solo lo necesario. Tener solver no significa tener tutor.

Los hints necesitan límites pedagógicos

Una pista técnicamente correcta puede revelar demasiado o apoyarse en una adivinanza que el producto no quiere fomentar. El solver aporta evidencia; la capa de producto decide qué mostrar.

Las migraciones de formato merecen tests de dominio

Si cambia la serialización, JSON válido no demuestra que el significado sobreviva. El puzzle migrado debe conservar pistas, variante, progreso y propiedades de solución.

Los cambios de reglas necesitan procedencia

Una seed o partida guardada puede representar un contrato anterior. Si una variante evoluciona, conviene conservar suficiente versión para entender por qué un caso histórico se comporta distinto.

La mejor abstracción aparece después de varios motores

Cuando una utilidad se repite en Dominosa, Kropki y Slant, tenemos evidencia para extraerla. Antes, un framework universal puede codificar repetición imaginada.

El determinismo facilita distinguir azar de regresión

Si la misma seed y versión producen resultados distintos, depurar rendimiento y lógica se vuelve ruidoso. Un modo determinista permite congelar orden de candidatos y aleatoriedad para reproducir una incidencia paso a paso, aunque el producto siga ofreciendo variedad a los jugadores.

La cola de latencia importa más que una media bonita

Un solver con buen promedio puede ser problemático si un pequeño porcentaje de partidas tarda varios segundos. Los benchmarks deben incluir percentiles altos y casos patológicos, porque una sola congelación larga puede dominar la percepción de calidad.

La procedencia del motor ayuda a explicar casos históricos

Guardar suficiente contexto de versión junto a fixtures duraderas permite distinguir “el algoritmo cambió” de “el estado se corrompió”. No hace falta versionar cada refactor, pero sí conservar contexto cuando afecta a la interpretación del caso.

Cada bug corregido debería convertirse en regresión

Una corrección gana valor cuando conserva el estado que la provocó y la propiedad que ahora debe mantenerse. Con el tiempo, la suite se convierte en una historia ejecutable de los límites del motor y permite optimizar con más confianza.

La arquitectura compartida está en el contrato de evidencia

Dominosa usa exact cover, Slant conectividad, Aquarium niveles y Kropki relaciones. Lo común es definir estado válido, completitud, solución, unicidad cuando toca, reproducibilidad e instrumentación.

El objetivo no es que todos compartan algoritmo

La diversidad matemática es una fortaleza. Compartimos disciplina: modelar con precisión, propagar pronto, buscar cuando hace falta, contar cuando la unicidad importa, medir casos reproducibles y convertir bugs en regresiones.

Validator y solver necesitan acuerdo explícito, no solo intuición compartida

Que dos piezas de código “usen las mismas reglas” no basta como garantía. La función que decide si el jugador ha terminado puede estar optimizada para una comprobación rápida, mientras el solver usa una representación distinta para explorar estados. Esa separación es sana, pero obliga a probar que ambas interpretaciones coinciden. Fixtures completas, casi completas e inválidas deberían pasar por los dos caminos y producir decisiones compatibles.

La ventaja de esta prueba cruzada es que convierte un supuesto arquitectónico en evidencia. Si un tablero recibe celebración visual y después el solver lo rechaza, el problema deja de ser una rareza de UI: existe una contradicción de dominio que debe fallar en CI.

Los hints necesitan una política de producto además de lógica correcta

Un solver puede justificar una respuesta mediante una bifurcación profunda, pero eso no significa que esa explicación sea una buena pista. Una ayuda útil debería respetar las técnicas que queremos enseñar, la dificultad elegida y cuánto conocimiento revelar. Puede ser correcto decir “esta celda es 7”, pero pedagógicamente más valioso explicar qué candidatos se eliminaron y por qué.

Separar solver de hint engine permite conservar ambos objetivos. El primero busca verdad y evidencia; el segundo convierte una parte de esa evidencia en una intervención comprensible. Así evitamos que la función de ayuda se convierta en un simple botón de resolver.

Las trazas de resolución pueden diseñarse para ser explicables sin acoplarse a la UI

Si en el futuro queremos hints más ricos, resulta útil que el dominio emita eventos semánticos: candidato eliminado por relación vecina, intervalo descartado, ciclo evitado, colocación obligatoria. Son conceptos más estables que “entró en la rama 17”.

Esas trazas no tienen que convertirse en una narración exhaustiva ni almacenarse siempre en producción. Pueden existir como instrumentación de desarrollo y, cuando convenga, alimentar una capa de explicación localizada. El punto es conservar significado suficiente para que la lógica pueda enseñarse sin depender de cómo estaba implementado el backtracking.

La cola de latencia importa más que una media favorable

Un solver que tarda 40 ms de media puede seguir siendo una mala experiencia si uno de cada cien casos bloquea varios segundos. La generación interactiva se percibe por sus peores momentos, no por una media abstracta. Por eso los benchmarks necesitan percentiles altos, límites máximos y un conjunto de casos patológicos conocidos.

Esta observación también cambia el gate de generación: si un candidato no puede verificarse dentro del presupuesto, no rebajamos unicidad. Lo descartamos, lo procesamos en otro contexto o ajustamos el generador para evitar esa región costosa del espacio.

El determinismo de diagnóstico no está reñido con la variedad del producto

Podemos ofrecer partidas diferentes a los jugadores y, al mismo tiempo, disponer de un modo donde la misma seed, versión y configuración sigan exactamente el mismo orden de decisiones. Esa capacidad facilita comparar perfiles, reconstruir fallos y entender por qué una optimización alteró el comportamiento.

La aleatoriedad es una herramienta de contenido. La reproducibilidad es una herramienta de ingeniería. Tratarlas como capas distintas evita elegir entre variedad y mantenibilidad.

La procedencia de una fixture puede explicar cambios legítimos

Un caso guardado hace meses puede haber sido generado con otra heurística, otro formato o una variante anterior. Sin contexto, una diferencia futura parece una regresión aunque sea consecuencia deliberada de una migración. Guardar metadatos suficientes —versión de formato, variante y, cuando importa, generación— permite interpretar correctamente esos casos.

No se trata de versionar cada línea de código. Se trata de que los artefactos duraderos conserven aquello que afecta a su significado.

Las migraciones tienen que conservar propiedades de solución

Una conversión de formato no está terminada solo porque el nuevo JSON sea válido. Para una partida única, el estado migrado debería seguir teniendo la misma solución y las mismas restricciones. Para una partida en curso, las acciones válidas y el progreso deberían conservarse.

Ejecutar validator y, cuando procede, solution count sobre fixtures antes y después de migrar da una garantía mucho más fuerte que comprobar únicamente tipos y campos. El dominio debe sobrevivir a la migración, no solo la estructura.

Cada bug real debería aumentar la memoria del motor

Cuando arreglamos una partida ambigua, una contradicción no detectada o una explosión de búsqueda, el arreglo es más valioso si conserva el caso que lo provocó. Esa fixture y la propiedad asociada se convierten en una barrera contra el mismo error.

Con el tiempo, la suite de regresión empieza a contar la historia del motor: qué supuestos fallaron, qué fronteras resultaron difíciles y qué optimizaciones no pueden romperse. Esa memoria ejecutable permite cambiar algoritmos con mucha más confianza.

La plataforma debería compartir observabilidad, no imponer matemática

Una abstracción común sí puede ofrecer contadores, timeouts, cancelación, logging de desarrollo, formato de seeds y hooks de benchmark. Son responsabilidades que aparecen en muchos motores sin alterar su modelo interno.

Este tipo de reutilización resulta más valiosa que obligar a Dominosa, Slant y Aquarium a fingir que resuelven la misma clase de variable. Compartimos las herramientas para entenderlos; dejamos que cada uno exprese sus restricciones con honestidad.

El criterio de extracción debería ser evidencia de repetición

Cuando una función o contrato aparece de forma similar en tres o cuatro motores reales, ya tenemos datos para decidir si pertenece a la plataforma. Antes de eso, generalizar puede crear un framework que obliga a escribir adaptadores para casos que nunca fueron iguales.

Esta manera de construir es más lenta al principio y más barata después: la capa común nace de problemas comprobados, no de imaginar todos los puzzles posibles. La diversidad del catálogo deja así de ser un obstáculo y se convierte en una prueba continua de nuestras abstracciones.

El solver común de Blupoli es una disciplina de evidencia

Después de Dominosa, Stitches, Slant, Aquarium, Str8ts y Kropki, la conclusión no es que debamos buscar el algoritmo que los unifique. La conclusión es que podemos unificar cómo exigimos confianza: modelos explícitos, propagación, reproducibilidad, unicidad cuando corresponde, presupuesto de rendimiento, tests adversariales y regresiones.

Ese contrato permite seguir añadiendo mecánicas sin perder rigor. El catálogo puede crecer en variedad porque la plataforma no confunde diversidad matemática con ausencia de estándares. Es la misma tensión que aparece al pasar de un puzzle aislado a una plataforma con decenas de juegos: la coherencia viene de los contratos compartidos, no de fingir que todos los motores son iguales.