Introducción

Comenzamos hoy la serie de tutoriales sobre desarrollo web con RoR. La idea de esta serie de artículos es la de introducirnos en la programación con Ruby on Rails de manera gradual, empezando con un prototipo de juguete y acabando con una aplicación todo lo completa que podamos (o sepamos) desarrollar. Tras cada etapa los resultados se podrán comprobar en SudokuOnRails.com.

Me gustaría dejar claro que, aunque hace algún tiempo que he empezado con RoR y he hecho alguna aplicación de uso personal (adaptación de otra en PHP) mis conocimientos de RoR y, sobre todo, de Ruby no son precisamente académicos ni mucho menos elegantes. Según vaya avanzando en mis estudios sobre RoR espero ir cubriendo estas deficiencias, lo que espero se vea reflejado en este blog.

Para empezar presentaremos el problema al que vamos a hincar el diente: el famoso pasatiempo del Sudoku, problema que supongo es conocido por todos pero en caso de que alguien viva debajo de una piedra y no lo conozca podrá informarse sobre el planteamiento y reglas del juego en este post de Microsiervos.

Podeis descargaros el código fuente para seguir el resto del post desde aquí y acceder a la aplicación online aquí.

¡Comencemos!

En nuestro terminal favorito, en un directorio vacío, empezamos con el comando que generará toda la estructura de nuestra aplicación:

rails sor

Si Ruby on Rails se encuentra correctamente instalado en nuestro ordenador, veremos que se crea un tupido árbol de directorios con muchos ficheros que no terminamos de conocer (es mejor que no borréis nada). Si, de inmediato, hacemos

ruby script/server

deberíamos ver la siguiente salida:

=> Rails application started on http://0.0.0.0:3000
=> Ctrl-C to shutdown server; call with --help for options

y con un navegador podemos visitar el puerto 3000 de nuestra máquina y ver la pantalla de bienvenida a Rails.

Si algo de lo anterior no funciona no deberías continuar. Visita la documentación de Rails (o deja un comentario describiendo tu problema por si yo puedo ayudarte)

Modelo

Nuestro modelo inicial será el tablero del Sudoku. Nos quedaremos con dos arrays de 9×9 casillas, uno con el Sudoku resuelto (que generaremos al principio) y otro con el estado del tablero tal y como lo ve el jugador.

Como generar un Sudoku correctamente no sólo se sale del ámbito de esta bitácora sino también del ámbito de mis conocimientos, decidí indagar al respecto y encontré este este interesante proyecto (cuyo autor es Balwinder Singh) que consiste en un juego de Sudoku interactivo en consola y del que hemos tomado prestado el código generador del tablero.

Pasemos a generar el modelo para nuestro tablero:

ruby script/generate model Tablero

Nuevamente vemos que se generan unos cuantos ficheros, nos fijaremos en app/models/tablero.rb. Podeis descargaros el código aquí.

La clase Tablero tiene como atributos públicos (definimos el accesor con :attr_reader) dos arrays de 9×9 como se ha descrito antes y una variable que guarda el número de movimientos que llevamos jugados. Los métodos shuffle y mask se encargan de generar el tablero propiamente dicho.

Si obviamos estos métodos (que supongo que interesarán a los aficionados a la teoría de números) lo único que nos queda es el método colocar. El cual verifica si un movimiento es válido, y si lo es no sólo devuelve true sino que modifica el array box_mascara (tablero visible por nosotros).

def colocar (x, y, n)
   resultado=false
   if @box[x][y]==n
      @box_mascara[x][y]=n
      resultado=true
   end
   @movimientos=@movimientos+1
   resultado
end

Para los novatos en Ruby: observad cómo se devuelve un valor simplemente evaluándolo al final del método.

Una última observación: al contrario que en todos los demás ejemplos de aplicaciones RoR que he encontrado, este modelo no tiene persistencia en la base de datos, sino que lo guardaremos en el servidor directamente en la sesión del usuario.

Controlador

El controlador es más simple que el modelo. Nuevamente:

ruby script/generate controller sudoku

Lo primero que hacemos es incluir nuestra clase del modelo, tablero, con la línea

model :tablero

Por defecto, la acción index se invoca cuando llamamos al controlador sin argumentos:

def index
  list
end

A continuación, el código de la acción list, que pintará el tablero en pantalla.

def list
  @box = tablero.get_box_mascara
  @movimientos=tablero.movimientos
  render_action 'list'
end

Nuestra plantilla para la acción list (que veremos en un momento) recibirá las variables @box y @movimientos desde el controlador. Vereis que el méotdo hace referencia a la variable tablero.movimientos. ¡No es una variable, sino un método privado!

El método se llama igual que el modelo (para simplificar las cosas) y lo que hace es obtener una instancia del tablero en la sesión y, si no existe, la crea (lo que invocará al método initialize del modelo) y la guarda.

def tablero
  @tablero ||= session[:tablero]
  unless @tablero
    @tablero = Tablero.new
    @tablero.shuffle
    session[:tablero] = @tablero
  end
  @tablero
end

También tenemos el método reset que simplemente elimina nuestra instancia del modelo del array de variables de sesión y ejecutamos la acción list que, otra vez, llamará a nuestro método privado tablero y, al no encontrar una instancia de la clase Tablero en la sesión (¡la acabamos de borrar!) volverá a crear otra y mostrar el tablero. Lo que el usuario verá será que al invocar este método (con un botón que pondremos en la plantilla) se generará un tablero nuevo.

def reset
    session[:tablero]=nil
    list 
end

Observad que esta acción ejecutará el código de la acción list, por eso en el código de ésta tenemos que decir específicamente que siempre muestre el layout list.rhtml (con render_action ‘list’), de lo contrario Rails trataría de mostrar la plantilla list.rhtml o reset.rhtml según la función del controlador invocada en primer lugar.

Tras esto, y pasando de puntillas sobre el método mover al que volveremos luego, podemos pasar a la vista.

Vista

Cuando el controlador ejecuta nuestra acción list Rails sabe que (salvo indicación por nuestra parte de lo contrario) tiene que mostrar la plantilla almacenada en app/views/controlador/accion.rhtml. En nuestro caso, app/views/sudoku/list.rhtml.

Las variables que declaremos en la acción del controlador serán visibles para los fragmentos de Ruby que empotremos en nuestra plantilla (que, por lo demás, será un fichero HTML). Así, este fragmento

<%if @movimientos > 1 %>
    Llevas <%= @movimientos %> movimientos
<% end %>

nos dirá cuántos movimientos llevamos jugados.

Es importante recordar de dónde proviene la variable @movimientos: la definimos en el código de la acción list del controlador sudoku y al extraimos del accesor movimientos de la instancia del modelo que tenemos almacenada en nuestra sesión. Todo un paseo, pero esto quiere decir que mientras no reiniciemos la sesión en el servidor (o borremos las cookies en nuestro navegador), Rails sabe que tenemos una sesión activa y recordará el estado del modelo.

El siguiente fragmento de nuestra plantilla para la acción list mostrará el tablero del Sudoku en un formulario, de forma que si introducimos un número en cualquier casilla y pulsamos ENTER o bien pinchamos en el botón correspondiente, invocaremos la acción mover de nuestro controlador.

Si observais el código de dibujo del tablero (que está rodeado por las etiquetas form\_tag y end_form\_tag) vereis que definimos un par de bucles anidados y decidimos el color de la casilla invocando una misteriosa función color\_casilla que recibe como parámetros la casilla que estamos pintando. Luego, si en la casilla adecuada del array @box (que ha seguido el mismo camino que su prima @movimientos) exite un número distinto de cero lo dibujaremos y, en caso contrario, pondremos un campo de texto asociado al formulario que estamos mostrando.

Tras esto y, para acabar, un nuevo formulario nos permite invocar el método reset que reinicia la sesión.

Funciones auxiliares

En app/helpers/sudoku\_helper.rb tenemos el código de la función color_casilla que, según la casilla, devuelve un 0 o un 1 que luego en la vista se usará para aplicar el corriente estilo de nuestra CSS. De esta función no me encuentro especialmente orgulloso, así que no la mostraré (podeis verla si os descargais el código fuente completo). Creo que hice mejores algoritmos en 2º de carrera.

Vuelta al controlador

Echemos un vistazo a cómo se genera el formulario con las casillas vacías del Sudoku (donde el usuari puede escribir un número). Cada una de estas casillas tendrá un identificador del tipo casillaXX donde XX se calcula en base a las coordenadas (1 la superior izquierda, 81 la inferior derecha) Ya estamos en condiciones de ver el método mover del controlador:

 for x1 in 0 .. 8
    for y1 in 0 .. 8
      cadena = "casilla"+((x1*9)+(y1+1)).to_s

      if @params.include?(cadena)
        if @params[cadena].length == 1
          posx=x1
          posy=y1
          valor=@params[cadena].to_i
        end
      end
   end

Y, por último, comprobamos el movimiento:

if tablero.colocar (posx, posy, valor)
    flash[:note]="¡Movmiiento correcto!" 
        else
    flash[:note]="¡Movimiento incorrecto!" 
end
list

Y, así, cerramos el ciclo: después de list del controlador, el usuario sólo puedo o invocar la acción reset o mover las cuales ambas terminan pasando otra vez por la acción list.

Plantilla de aplicación

El fichero app/views/layout/application.rhtml será procesado por el framework al mostrar cualquier plantilla, y no tiene ningún misterio en nuestra aplicación: tiene el contenido habitual en cualquier tutorial Rails, a saber:

  • Un enlace a la hoja de estilos (donde tenemos definidos los estilos correspondientes a las celdas del tablero)
  • Muestra un rótulo de advertencia en caso de que el controlador ponga la variable flash (como se ve en la acción mover)
  • Y, por último, incopora el tag content\_for\_layout que muestra la vista de la plantilla determinada por la acción.

Con esto, doy por concluido el repaso a las aplicación. Deberíamos poder acceder a la primera versión desde aquí.

¿Y ahora?

Pues todavía queda mucho por hacer,sólo tenemos un boceto inicial de una aplicación. Lo primero que ocurre es que el juego no tiene un “final”, esto es, cuando terminamos el Sudoku nadie nos indica nada. Además, también queremos anotar el tiempo que tardamos en resolver el Sudoku como primer paso para una especie de “tabla clasificatoria” de los jugadores. Todo esto en un próximo post.

1 Response to “Sudoku on Rails, v1.0”

  1. mauro Says:
    interesante...buen comienzo con RoR

Sorry, comments are closed for this article.