¿Cómo es el juego de Simon?

El juego es sencillo. Al pulsar el botón de inicio se muestra una secuencia de colores y sonidos en un tablero, luego el jugador tiene que repetir la secuencia usando los botones del tablero, si tiene éxito el juego muestra de nuevo la secuencia añadiendo un nuevo paso. Así continúa hasta que el jugador falle en reproducir alguno de los pasos, entonces el juego termina y vuelve a ponerse a la espera de que se pulse el botón de inicio.

https://es.wikipedia.org/wiki/Simon_(juego)

¿Qué necesitamos programar?

El juego tiene tres estados:

  • Esperando
  • Ejecutando la secuencia
  • Comprobando la secuencia del jugador

La secuencia de funcionamiento es muy simple. El juego permanece en estado Idle (esperando) hasta que el jugador pulsa el botón de inicio. Entonces ejecuta el estado IA (mostrar secuencia a memorizar) una vez terminado pasa al estado Jugador (comprobar la secuencia del jugador) si este completa toda la secuencia vuelve al estado de IA donde se añade un elemento más, si el jugador falla se vuelve al estado Idle.

El estado IA y Jugador se van a estar cambiando constantemente hasta que el jugador falle.

Los elementos visuales del juego

Vamos a ver qué elementos mínimos necesitamos y cómo los vamos a usar.

En el aspecto de control vamos a necesitar un botón de inicio, cuatro botones para que el usuario meta la secuencia y un contador

NOTA: Uno de los problemas que tuve para crear este prototipo fue como mostrar la secuencia al jugador. Al principio me empeñe en buscar la forma de activar por programación el estado de pulsado de los botones. Eso me hizo perder mucho tiempo para un juego tan sencillo. Por fin se me encendió la bombilla y fue una sensación de “soy tonto de remate”. Lo que hice es crear una serie de imágenes que contienen la imagen de boton pulsado con el mismo tamaño y posición de los botones que se hacen visible o invisible por programación.

La lógica del juego

Internamente el juego se basa en crear una secuencia que se incrementa por su final (spoiler: un array):

var sequence:Array = [];

<...>

func startAIState():
    <...>
    #Add one element to the sequence
    var newElement = randi()%4;
    sequence.append(newElement);
    <...>

que se recorre mostrando su contenido al jugador:

<...>

func startAIState():

    <...>
    
    #play the sequence
    for e in sequence:
        ai[e].visible = true;
        sfx[e].play();
        yield( sfx[e], "finished" );
        ai[e].visible = false;

<...>

NOTA: Pare evitar el código espagueti, es decir, usar largas secuencias de if/elif/else, uso arrays con referencias a los elementos que tengo que cambiar en función de una variable, en este caso el número de botón. En el código de arriba ai[] son imágenes que representan los botones activos, sfx[] son sonidos que se deben reproducir.

Una vez finalizado la reproducción de la secuencia se pasa a “escuchar” las acciones del jugador. Para eso he creado cuatro botones cuya señal button_up la conecto a una función.

<...>

#Connected from buttons with a diferent parameter each
func _checkPlayerButton(button:int):
    if playerCursor < sequence.size() and sequence[playerCursor] == button:
        sfx[button].play();
        playerCursor += 1;
        if playerCursor >= sequence.size():
            yield( sfx[button], "finished" );
            startAIState();
    else:
        _error();

<...>

Aquí uso la capacidad de Godot para conectar las señales y añadirles un parámetro.

Simon_ConectWithParameters.png

Para eso hay que activar el toggle de avanzado (1), luego añadir el tipo de parámetro que se va a enviar a la función (2) e introducir el valor o valores que se van a enviar con esta señal.

Otras Cosas Interesantes

El codigo que puedes ver aqui es bastante simple.

Sin embargo, hay cosas interesantes que aprender. Sobre todo el uso de yield para mejorar la jugabilidad.

<...>
yield(get_tree().create_timer(0.5), "timeout")
<...>
yield( sfx[e], "finished" );
<...>

Utilice un yield con un timer creado en tiempo de ejecución para dar un espacio entre el fin de un estado y el comienzo de otro, eso mejoró la comprensión de que se había cambiado de estado y evitaba que las pulsaciones del jugador y el inicio de la secuencia de IA se solaparan.

También use yield con los AudioStreamPlayer que están referenciado en el array sfx para detener la ejecución de la secuencia de IA mientras sonara el efecto correspondiente al botón. Esto evitó que los sonidos se pisaran unos a otros y también que la secuencia IA se ejecutará a toda velocidad.

Otro cosa que tuve que implementar son dos funciones que habilitan y deshabilitan los botones del jugador, para evitar que durante la IA el jugador pueda pulsar y llamar a la función correspondiente del botón.

Ideas Finales

Esto es solo un prototipo, así que le falta mucho para ser un producto que se pueda distribuir. Entre las cosas que faltan serían:

  • Mejores gráficos
  • Una pantalla de presentación
  • Una tabla de puntuaciones
  • Varios modos de juegos

Bueno, hasta aquí este post. Espero que les sea util mi experiencia haciendo este prototipo. Nos vemos en el siguiente post. Bye