Timer con threads para mover la serpiente

Clase 31 de 39Curso de Ruby

Contenido del curso

Bases del lenguaje

Proyecto

Resumen

Lograr que la serpiente se mueva de forma automática es el paso que transforma un tablero estático en un juego real. Para conseguirlo se necesita un timer que invoque periódicamente la acción de movimiento, un segundo thread que trabaje en paralelo al hilo principal de renderizado y una correcta separación entre la inicialización de la ventana y su actualización visual.

¿Cómo preparar el punto de entrada de la aplicación?

El archivo App.rb actúa como punto de entrada. Hasta ahora solo declaraba la clase sin ejecutar nada, así que el primer cambio consiste en instanciarla y llamar al método start [0:14].

ruby app = App.new app.start

Para confirmar que todo funciona se puede colocar un simple puts "Hola" y ejecutar con bundle exec ruby. Si el mensaje aparece en consola, el flujo es correcto.

¿Por qué separar inicialización y render en la vista?

En la versión anterior, el método render hacía dos cosas a la vez: inicializaba la ventana y dibujaba los elementos. Eso es un problema porque la inicialización debe ocurrir una sola vez, mientras que el render se ejecuta cada vez que el estado cambia [1:07].

La solución es crear un método start en la vista que reciba el estado, calcule alto y ancho, abra la ventana con show y deje el método render exclusivamente para las funciones delegadas render_food y render_snake.

ruby def start(state)

inicializar ventana con state

show end

def render(state) render_food(state) render_snake(state) end

De vuelta en App.rb, se pasa el initial state al start de la vista para que la ventana aparezca correctamente [1:55].

¿Cómo implementar el timer con un thread concurrente?

Ruby 2D utiliza el hilo principal para el ciclo de renderizado de la ventana. Si se bloquea ese hilo con un sleep o un bucle, la interfaz se congela. La solución es lanzar un thread adicional que corra de forma concurrente [2:21].

ruby def init_timer(view) Thread.new do loop do @state = move_snake(@state) view.render(@state) sleep(0.3) end end end

Dentro de ese hilo se ejecuta un bucle infinito que:

  • Llama a la acción move_snake pasándole el estado actual.
  • Actualiza la vista con view.render.
  • Espera un intervalo con sleep antes de repetir.

¿Qué papel juegan las variables de instancia?

Para que todos los métodos de App compartan el mismo estado, se reemplaza la variable local por una variable de instancia @state [2:56]. En Ruby, las variables de instancia se declaran con el prefijo @ y pertenecen al objeto, de modo que cualquier método de esa instancia puede leerlas y modificarlas.

El constructor initialize se encarga de guardar el estado inicial:

ruby def initialize @state = initial_state end

Además, se incluye el módulo de acciones con include Actions para tener acceso directo a move_snake. Por la convención definida en el proyecto, cada acción retorna siempre el nuevo estado, lo que facilita la reasignación:

ruby @state = move_snake(@state)

¿Cómo se conecta la vista con el timer?

La variable view se pasa como parámetro al método init_timer, ya que fuera de ese contexto no tendría acceso a ella. El orden dentro del bucle importa: primero se ejecuta el render y después el sleep, para que la serpiente se mueva inmediatamente al iniciar [3:47].

Al ejecutar bundle exec ruby se puede observar cómo la serpiente avanza de forma automática por el tablero, confirmando que el timer funciona correctamente [4:03].

¿Qué conceptos clave se aplican en esta implementación?

  • Concurrencia con threads: permite que el timer y la ventana trabajen en paralelo sin bloquearse mutuamente.
  • Separación de responsabilidades: inicialización y actualización visual viven en métodos distintos.
  • Variables de instancia en Ruby: comparten estado entre métodos de un mismo objeto mediante el prefijo @.
  • Convención de acciones inmutables: cada acción devuelve un nuevo estado, manteniendo un flujo predecible.

El siguiente paso será capturar las teclas del usuario para controlar la dirección de la serpiente. ¿Qué estrategia usarías para manejar el input del teclado sin interrumpir el timer? Comparte tu enfoque en los comentarios.