Ir al contenido principal

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

C
omo programador, he tenido que realizar proyectos profesionalmente, algunos con mayores retos que otros. Pero aparte de los retos profesionales, existen retos personales, programas que me nace escribir ya sea porque necesito solucionar una necesidad puntual o solamente por el placer de hacerlo. Uno de esos últimos retos fue el de solucionar un Sudoku. Si ya se, existen muchas aplicaciones allí afuera que lo hacen, pero el reto es hacerlo, no copiarlo. Habiendo aclarado las intenciones al respecto, lo primero a tener claro es cómo se define un Sudoku. Para esto, voy a apoyarme en la siempre disponible (aunque no siempre fiable) Wikipedia:

Un Sudoku estándar contiene 81 celdas, dispuestas en una trama de 9×9, que está subdividida en nueve cajas. Cada caja está determinada por la intersección de tres filas con tres columnas. Cada celda puede contener un número del uno al nueve y cada número solo puede aparecer una vez en cada fila, cada columna o cada caja. Un sudoku comienza con algunas celdas ya completas, conteniendo números (pistas), y el objetivo es rellenar las celdas restantes. Los sudokus bien planteados tienen una única solución.

Wikipedia

Ojo con esa última línea: “Los sudokus bien planteados tienen una única solución”, la tendremos que considerar más adelante.

Un tablero de Sudoku en limpio

Solucionar un Sudoku no es algo trivial, aunque así lo parece. Lo primero que intenté fue replicar mi forma de abordar estos pasatiempos, pero entonces me di cuenta que plasmar mi forma de pensar en un “algoritmo coherente” no iba a resultar porque, bueno, los humanos tenemos formas de pensar muy particulares y no siempre se trasladan bien al lenguaje de una máquina (de allí la trascendencia, relevancia e importancia de lo que se ha logrado con ChatGPT). Esto me llevo a que muchas veces abandonara este proyecto. Y a que muchas veces lo intentara de nuevo, hasta que finalmente decidí que lo mejor era comenzar por el principio, con algo “sencillo”, como llenar una cuadricula de Sudoku en limpio. Decidido eso, lo siguiente fue trazar la ruta de trabajo a seguir, que fue la siguiente:

Teniendo esta ruta definida, comencé con lo básico en estos casos, porque la urgencia en estos proyectos personales suele ser sentarse a programar (usando PHP en mi caso) y comenzar a ver resultados, así no sea la presentación lo más elegante del mundo. Con esto en mente, comencé la creación de una clase que inicialmente permitiera:

  • Definir el tamaño base del Sudoku: 2 cajas de ancho, 4 celdas o 3 cajas de ancho, 9 celdas. Este último será la opción por defecto.
  • Registrar el tablero de Sudoku en un arreglo bidimensional. Probablemente no la más adecuada forma pero si la representación más aproximada a la cuadricula de Sudoku a la que estamos acostumbrados.
  • Generar un identificador único para cada celda y caja del tablero.
  • Generar un identificador único o checksum para el tablero en su conjunto (esto para futuras implementaciones donde se requiera diferenciar un tablero de otro).

Adicionalmente, la clase debe definir cada celda y asignarle atributos como:

  • Valor de la celda (usaremos el carácter “.” para identificar una celda si valor asignado).
  • Si es una celda fija (a usar cuando queramos solucionar un Sudoku existente).
  • Posibles valores permitidos en la celda (números del 1 al 9 que cambiarán conforme vayamos llenando el Sudoku).

Finalmente y aunque no es lo usual debido a que va en contra de algunos principios aplicables a las clases (¿has escuchado hablar de principios SOLID?), voy a incluir un método para visualizar el tablero de Sudoku en pantalla. Esto ayudará a visualizar el progreso de la solución. Posteriormente puede retirarse para tener una clase no dependiente de la salida a pantalla pero de momento nos servirá así.

Esto nos lleva a tener una clase con una estructura similar a la siguiente:

class miSudoku {
	// Numero de celdas a contener en cada grupo de celdas. Esto es, el tablero tendrá
	// $this->base cajas a lo ancho, cada una con $this->base celdas. Así, si la base es 
	// 2, el número de celdas a lo ancho (así como el número de celdas en cada caja) es
	// de 4, entanto que para base 3 el número de celdas es 9.
	public $base = 3;

	// Arreglo bidimensional con la información del tablero de Sudoku
	private $tablero = array();

	/**
	 * Construye base para el tablero de Sudoku.
	 * Las opciones validas para llenar cada celda son los números de 1 en adelante. El 
	 * valor máximo depende de la base usada (4 o 9, usualmente para una base de 2 o 3
	 * respectivamente).
	 */
	public function construirBase() {
		// ...
	}

	/**
	 * Genera texto HTML a pantalla para visualizar el tablero de Sudoku.
	 */
	public function render() {
		$salida = '';
		// ...
		echo $salida;
	}
}

Una vez codificada (puedes consultar el script completo en https://github.com/jjmejia/sudoku/tree/main/parte-1), tendremos una presentación en pantalla como la siguiente:

Y con esto podemos comenzar a llenar el tablero, pero eso lo veremos en detalle en la próxima entrega de esta serie.


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

Comentarios

Entradas populares de este blog

Manejo de recursos HTML para tus páginas web con PHP

Déjame saber si te resulta familiar esta situación: páginas web que descargan el mismo recurso (sean estilos CSS o código Javascript) más de una vez o incluyen recursos remotos que tardan una eternidad en cada descarga. Yo lo he visto en más de una ocasión y no es difícil imaginar el porqué ocurre. Un desarrollador incluye el recurso de estilos que necesita su segmento de código y otro hace lo mismo, sin reparar (o sin que siquiera importe) que comparten el mismo recurso. En otro escenario muy común, acostumbran incluir muchos recursos remotos, con lo que el rendimiento de la página depende de lo rápido que responda dicho recurso. ¿Puede hacerse algo al respecto? Claro que si. Vamos a crear una clase en PHP que se encargue de administrar estos recursos y que nos facilite su despliegue en la página sin repeticiones . ¿Y respecto a la demora en la carga de recursos remotos? Atendamos una cosa por vez, porque como dicen por ahí: «Vísteme despacio, que tengo prisa». Administrando ...

Manejo de clases globales únicas en PHP

¿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 del script que ej...

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

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 $key => $value) { ....