Skip to content

Máquina de estados

Una vez que tenemos el código base de nuestro juego, vamos a comenzar por implementar la lógica de la máquina de estados, que nos permitirá controlar el flujo del juego y las diferentes pantallas que se mostrarán al jugador.

Una máquina de estados es un modelo de comportamiento que nos permite definir diferentes estados en los que puede encontrarse nuestro juego, así como las transiciones entre estos estados. En nuestro caso, vamos a definir los siguientes estados:

  • INIT: Estado inicial del juego, donde se realiza la configuración inicial y se muestra la pantalla de inicio.
  • GAMEPLAY: Estado donde se desarrolla la jugabilidad del juego, donde el jugador controla el objeto y debe recoger caramelos y evitar obstáculos.
  • GAMEOVER: Estado donde se muestra la pantalla de fin de juego, indicando al jugador que ha perdido todas sus vidas y mostrando su puntuación final.
  • PAUSE: Estado donde se muestra la pantalla de pausa, permitiendo al jugador pausar el juego y reanudarlo cuando lo desee.

Cada estado, tendrá su propia lógica y se encargará de controlar el flujo del juego en función de las acciones del jugador y los eventos que ocurran durante la jugabilidad. Para implementar la máquina de estados, vamos a utilizar una variable que nos permitirá almacenar el estado actual del juego y un conjunto de funciones que se encargarán de manejar cada uno de los estados definidos.

Vamos a definir un enumerado que nos permitirá representar los diferentes estados del juego:

enum states {
    INIT,
    GAMEPLAY,
    GAMEOVER
    };

Info

Un enumerado es un tipo de dato que nos permite definir un conjunto de valores constantes, en este caso, los diferentes estados del juego. Cada valor del enumerado representa un estado específico y nos permitirá controlar el flujo del juego de manera más sencilla y legible.

Este enumerado, nos permitirá definir los diferentes estados del juego y nos ayudará a controlar el flujo del juego de manera más sencilla y legible. A continuación, vamos a definir una variable que nos permitirá almacenar el estado actual del juego:

int currentState = INIT;

Ahora podemos utilizar esta variable para controlar el flujo del juego y determinar qué acciones se deben realizar en función del estado actual. Por ejemplo, si el estado actual es INIT, podemos mostrar la pantalla de inicio y esperar a que el jugador presione un botón para comenzar el juego. Si el estado actual es GAMEPLAY, podemos actualizar la lógica del juego y dibujar los elementos en pantalla. Y si el estado actual es GAMEOVER, podemos mostrar la pantalla de fin de juego y permitir al jugador reiniciar el juego o salir.

Para una mejor organización del código, vamos a definir las funciones Update() y Draw(), que se encargarán de actualizar la lógica del juego y dibujar los elementos en pantalla, respectivamente. Estas funciones se llamarán en el bucle principal del juego (loop()), y dentro de ellas, utilizaremos la variable currentState para determinar qué acciones se deben realizar en función del estado actual del juego.

Para ello, vamos a crear funciones auxiliares para cada estado del juego, que se encargarán de manejar la lógica y el dibujo de cada estado específico. Por ejemplo, podemos crear las funciones UpdateInit(), UpdateGameplay() y UpdateGameover(), que se encargarán de manejar la lógica y el dibujo de los estados INIT, GAMEPLAY y GAMEOVER, respectivamente.

#include <Arduboy2.h>

#define FRAMES 25

void Update();
void Draw();

void UpdateInit();
void UpdateGameplay();
void UpdateGameover();

void DrawInit();
void DrawGameplay();
void DrawGameover();

Arduboy2 arduboy;

enum states {
    INIT,
    GAMEPLAY,
    GAMEOVER
};

int currentState;

void setup() {
  arduboy.begin();
  arduboy.setFrameRate(FRAMES);
  currentState = INIT;
}

void loop() {
  if (!(arduboy.nextFrame())) return;

  Update();
  arduboy.clear();
    Draw();
  arduboy.display();
}

void Update() {
    switch (currentState) {
        case INIT:
            UpdateInit();
            break;
        case GAMEPLAY:
            UpdateGameplay();
            break;
        case GAMEOVER:
            UpdateGameover();
            break;
    }
}

void Draw() {
    switch (currentState) {
        case INIT:
            DrawInit();
            break;
        case GAMEPLAY:
            DrawGameplay();
            break;
        case GAMEOVER:
            DrawGameover();
            break;
    }
}

Por último, vamos a implementar las funciones UpdateInit(), UpdateGameplay() y UpdateGameover(), que se encargarán de manejar la lógica y el dibujo de cada estado específico del juego. En estas funciones, podemos definir las acciones que se deben realizar en función del estado actual del juego, como mostrar la pantalla de inicio, actualizar la lógica del juego o mostrar la pantalla de fin de juego.

void UpdateInit() {
    // Lógica del estado INIT
    if (arduboy.justPressed(A_BUTTON)) {
        currentState = GAMEPLAY;
    }
}

void UpdateGameplay() {
    // Lógica del estado GAMEPLAY

}

void UpdateGameover() {
    // Lógica del estado GAMEOVER
    if (arduboy.justPressed(A_BUTTON)) {
        currentState = INIT;
    }
}
Y finalmente, podemos implementar las funciones DrawInit(), DrawGameplay() y DrawGameover(), que se encargarán de dibujar los elementos en pantalla correspondientes a cada estado del juego.

void DrawInit() {
    // Dibujo del estado INIT
    arduboy.setCursor(0, 0);
    arduboy.print("Press A to Start");
}

void DrawGameplay() {
    // Dibujo del estado GAMEPLAY
}

void DrawGameover() {
    // Dibujo del estado GAMEOVER
    arduboy.setCursor(0, 0);
    arduboy.print("Game Over");
    arduboy.setCursor(0, 10);
    arduboy.print("Press A to Restart");
}