Ir al contenido principal

Cómo resolver y/o crear un Sudoku usando PHP (parte 3)

C
ontinuamos con esta serie de artículos donde comparto con ustedes la implementación de una solución a los Sudokus usando PHP. Como se planteó inicialmente, la ruta de desarrollo a seguir es la siguiente:

  • Llenar una cuadricula de Sudoku en blanco.
  • Solucionar un Sudoku existente.
  • Finalmente, crear un Sudoku para ser impreso.

El proceso de llenar una cuadrícula de Sudoku en blanco fue cubierto en el artículo anterior. A partir de este punto, nos queda definir el cómo registrar los valores existentes en un Sudoku que deseemos resolver, esto es, indicarle a nuestro código los valores de aquellas celdas iniciales o fijas. Para esto, se propone que dichos valores se ingresen mediante un bloque de texto donde cada línea corresponda a una fila, cada posición en la línea a una columna, cada celda vacía se identifique con un punto (“.”) y los valores fijos por números entre 1 y 9 (para un Sudoku de 9x9). Por ejemplo, usando como base el siguiente tablero:

Un tablero de Sudoku por resolver

Su representación en texto sería la siguiente:

..9|7..|3..
..8|...|659
.1.|...|7..
-----------
.96|837|1.4
1..|...|...
.2.|...|..3
-----------
...|..4|..1
4..|..3|8..
2..|9.6|..5

Para efectos de facilitar su visualización para los ojos humanos como los míos y los tuyos, me he tomado la libertad de adicionar caracteres para separar las filas y cajas, pero estos serán ignorados por el script al interpretarlo. Tanto es así, que sería igualmente válido ingresar un texto como:

..97..3..
..8...659
.1....7..
.968371.4
1........
.2......3
.....4..1
4....38..
2..9.6..5

Sin embargo, esta última versión no se ve muy amigable, ¿verdad?

De cualquiera de las dos maneras mostradas o cualquier combinación de las mismas, el código deberá interpretar correctamente la carga y asignar los valores fijos deseados. Una vez inicializado el Sudoku con estos valores fijos y antes de comenzar al proceso de llenado de filas, es prudente (de acuerdo a la lógica planteada en la implementación realizada y descrita en la entrega anterior) revisar los valores disponibles para las celdas a las que esos valores fijos pueden afectar, esto es, a aquellas que están en la misma fila, columna y caja. Así, la clase implementada se modificaría para incluir los siguientes métodos:

class miSudoku {

	// Propiedades y métodos previamente declaradas...

	/**
	 * Asigna valores fijos.
	 * Requiere ejecutar primero $this->construirBase().
	 * Recibe texto con la estructura del Sudoku deseado, usando "." para indicar las
	 * celdas vacias, números para las celdas ocupadas (1..9 en un Sudoku de 9x9). Una 
	 * linea por fila. Las líneas en blanco	 * y tabuladores o espacios al inicio/final
	 * de cada línea son ignoradas. Opcionalmente puede separar las cajas usando el
	 * carácter "|" en las fijas e incluyendo una fila con "-" para separar las cajas 
	 * en horizontal.
	 */
	public function setFijas(string $texto) {
		// …
	}

	/**
	 * Recorre tablero y si encuentra que existen celdas fijas, revisa valores 
	 * disponibles asociados.
	 */
	private function inicializarDisponibles() {
		// …
	}
}

Ya con estos cambios podemos pasar felizmente a ejecutar el programa y dejar que resuelva el Sudoku por nosotros. 

Pero... (siempre hay un “pero”, ¿verdad?)

Al ejecutar el script es muy probable que a) use demasiados ciclos para resolverlo o b) puede no resolverlo del todo. Antes de entrar en pánico y mandar todo a la papelera de reciclaje, permíteme explicar el porqué puede ocurrir esto. Como no se está abordando una cuadrícula en blanco sino una con valores previos, es posible que la cantidad de validaciones a realizar supere el límite fijado o que por cuenta de aquellas celdas fijas, existan celdas que solamente puedan tomar uno y solamente un valor, pero que al intentar buscar la solución a “fuerza bruta”, el algoritmo tropiece y falle al relacionar dicho “valor deseado”. Al menos esta es la conclusión a la que llegué luego de seguir un “paso a paso” del proceso (algo que puede resultar tedioso, frustrante y hasta insoportable, pero completamente necesario). Al observarlo, noté que estos tropiezos no suceden cuando soluciono manualmente un Sudoku en papel, precisamente porque primero busco aquellos números que se repiten y que hacen que ciertas celdas en blanco puedan tomar uno y solamente un valor posible. Esos son los primeros valores que ubico en una solución en papel y supuse que sería prudente hacer que el programa hiciera lo mismo.

Así, adicioné un nuevo método a ejecutar cada que se asigne un nuevo valor a una celda:

class miSudoku {

	// Propiedades y métodos previamente declaradas...

	/**
	 * Recorre el tablero buscando celdas en blanco que pueden tomar uno y solamente
	 * un valor disponible.
	 * 
	 * @return bool TRUE si el valor es aceptable por las reglas del Sudoku, FALSE en 
	 * otro caso.
	 */
	private function validarUnicos() {
		// …
		return true;
	}

	/**
	 * Revisa el tablero solucionado y garantiza que todos los valores cumplen con
	 * las reglas del Sudoku. Imprime mensaje de texto opcionalmente.
	 *
	 * @param bool $solo_validar TRUE no imprime mensajes de texto.
	 * @return bool TRUE si el tablero quedó bien solucionado, FALSE en otro caso.
	 */
	public function validarSolucion(bool $solo_validar = false) {
		// …
		return true;
	}
}

Adicionalmente incluí un método de chequeo que revise la cuadrícula final y garantice que la solución presentada satisface todas las reglas del Sudoku, para de esta forma evitarme la tarea de hacerlo manualmente (esas son las cosas que hacemos los programadores, escribir código para ejecutar tareas que no queremos hacer, jeje).

Ya con estos ajustes finales, es posible resolver el Sudoku con nuestro código y obtener algo como lo siguiente:

Un tablero de Sudoku completamente lleno

o la siguiente:

El mismo tablero de Sudoku completamente lleno

¿Cómo es esto posible? ¿Dos soluciones para un mismo Sudoku? 

Si, si observan cuidadosamente, estas dos imágenes muestran el mismo Sudoku con dos soluciones ligeramente diferentes. Estos Sudokus pueden resultar muy difíciles de resolver y son un verdadero dolor de cabeza. Aparecen cuando no han sido correctamente creados, porque como veremos en la siguiente entrega, existen algunas reglas a seguir cuando se crea un Sudoku.

Como de costumbre, el script completo para esta etapa puede consultarse en https://github.com/jjmejia/sudoku/tree/main/parte-3 y aquellos más curiosos pueden checar los cambios respecto al repositorio del modelo anterior, checando el listado de commits realizados.

Los espero para el capítulo final de esta serie de artículos, hasta entonces, diviértanse solucionando Sudokus.

Este artículo también se encuentra disponible en medium.com.

Comentarios

Entradas populares de este blog

Sesión de usuarios en aplicaciones web

Uno de los módulos más importantes y a la vez menospreciados cuando se aborda la tarea de crear un sitio web de servicios, ya sea para una intranet corporativa o un sistema de gestión de información ( SGI ) es la gestión y administración  requerida para una correcta implementación de sesiones de usuario. Y es que llevamos tanto tiempo usando usuarios y contraseñas en Internet, en cualquiera de sus muchas variaciones, que se asume muchas veces que esto ya forma parte del ADN de toda solución web y como tal, se destina muy poco tiempo y estudio a este apartado cuando se planifican las actividades de desarrollo. Lo cierto es que cada aplicación acostumbra desarrollar su propio esquema de manejo de sesiones y asumir que es algo superfluo puede equivaler a “pegarse un tiro en el pie”, especialmente cuando un módulo de este tipo se diseña desde ceros. Al referirse al manejo de sesiones de usuario suele pensarse únicamente en el proceso de capturar el nombre de usuario ( username ) y su cont

Manejo de clases globales únicas en PHP

Imagen de Tumisu en Pixabay ¿Cómo acceder desde cualquier script en tu proyecto a Clases y/o funciones de uso común? Este puede ser una de las primeras directrices a establecer para cualquier proyecto porque siempre, siempre , sea en  PHP  u otro lenguaje, será necesario usar recursos comunes. En PHP existen diferentes alternativas para su manejo, ya sea por medio de variables globales o de clases/objetos estáticos. A continuación consideraremos una propuesta para este manejo. Creación de recursos globales Para ilustrar esta solución, partimos de la necesidad de implementar una librería para manejo de servicios relacionados con el servidor Web, que de forma amigable nos permita disponer de información como: Valores almacenados de la variable superglobal $_SERVER de PHP. Valores asociados a la consulta realizada por el usuario, por Ej. la dirección IP del usuario o la URL ingresada. Valores asociados al servidor web usado, por Ej. la dirección IP del servidor o la ubicación de

¿Qué tan bueno es realmente el “foreach” en PHP?

Imagen de garionhk en Pixabay Como toda buena historia, esta comienza hace algún tiempo. El que fuera mi jefe por allá en la primera década del 2000, realmente odiaba (y mucho) el uso del foreach en el código PHP . Prefería que usáramos alguna alternativa diferente, alguna combinación del  for o del while . ¿Por qué? Ve tú a saber, nunca fue abierto respecto a las razones de su aprensión hacia ese constructor propio del lenguaje. Pero antes de continuar, veamos qué es y para qué nos puede servir. Arreglos, tenían que ser arreglos ¿Qué es foreach ? De acuerdo al manual de PHP , su definición es la siguiente: El constructor foreach proporciona un modo sencillo de iterar sobre arrays . foreach funciona sólo sobre arrays y objetos , y emitirá un error al intentar usarlo con una variable de un tipo diferente de datos o una variable no inicializada. Para su uso correcto existen dos sintaxis validas, a saber: foreach (expresión_array as $value) { ... } foreach (expresión_array as