Apr 12, 2020

IA de videojuegos al servicio de la UI

TecnologíaJavaScriptProgramación

IA de videojuegos al servicio de la UI

El desarrollo de UI se ha hecho más difícil debido a que ahora manejamos estados de la aplicación en el navegador, lo cuál aumenta la complejidad en el desarrollo y en el control de bugs, y la mayoría de bugs tienen que ver con estados mal manejados o con intersecciones entre estados.

La forma en que manejamos los estados en React no son escalables, reutilizables o facilmente testeables. Para hacer un Fetch generamos mucha configuración para manejar el estado de loading, hasError, hasSucces y si queremos pasar esa lógica tenemos que escribirla una y otra vez.

Manejar mal un estado puede traer consecuencias catastróficas, si un semaforo tuviera prendidos las luces verde y roja al mismo tiempo sería algo que seguro causaría algunos accidentes o mucho tráfico, a veces necesitamos que sólo exista un estado y saber qué estado es el siguiente, es decir, necesitamos un flujo claro para saber como pasar de un estado a otro, y que sólo exista un estado a la vez. Eso se soluciona con las máquinas de estado finito.

Máquinas de estado finito

Los enemigos de videojuegos no usan IA de verdad en la mayoría de los casos, ellos no aprenden de las partidas, ellos reaccionan a eventos que los lleva a un estado que hace que te persigan, te ataquen, recarguen munción o mueran, eso lo logran con las Máquinas de estado finito.

stateDiagram-v2
    [*] --> Scatter
    Scatter --> Chase : timer_expired
    Chase --> Scatter : timer_expired
    Chase --> Frightened : pacman_ate_power_pellet
    Scatter --> Frightened : pacman_ate_power_pellet
    Frightened --> Scatter : timer_expired
    Frightened --> Dead : eaten_by_pacman
    Dead --> Respawn : reached_ghost_house
    Respawn --> Scatter : respawn_complete

    note right of Scatter
        Los fantasmas se mueven
        hacia las esquinas del
        laberinto
    end note

    note right of Chase
        Cada fantasma persigue
        a Pac-Man con su propia
        estrategia
    end note

    note right of Frightened
        Los fantasmas huyen
        de Pac-Man y se vuelven
        azules
    end note

    note right of Dead
        El fantasma regresa
        rápidamente a la casa
        de fantasmas
    end note

Las máquinas de estado finito trata de una máquina que puede tener muchos estados pero sólo puede estar en uno de ellos en un momento determinado, cómo los enemigos de los videojuegos, lo cuál hace que sea más predecible como se pasa de un estado a otro, como en el semaforo, sabemos que respetara la secuencia y que no hará cosas alocadas como prender o apagarse al ritmo de una canción, es decir, sólo tiene una cantidad finita de estados, y esa limitación nos sirve mucho para el manejo de UI.

Por ejemplo, podemos ver un state machine de un Fetch y es más sencillo:

stateDiagram-v2
    [*] --> Idle
    Idle --> Fetching
    Fetching --> [*]
    Fetching --> Error
    Error --> Fetching
    Error --> [*]

En esta lógica:

Todo eso suena genial pero ¿cómo lo implementamos?, una librería genial se llama xState.

Introducción a xState

Para definir una máquina de estado con xState escribimos primero los estados que ocurrirán de esta forma:

import { createMachine } from "xstate";

const maquinaDeFetch = createMachine({
  id: "fetch",
  initial: "idle",
  // Lista de estados
  states: {
    idle: {},
    fetching: {},
    error: {},
  },
});

Definimos como se hará la transición

import { Machine } from "xstate";

const maquinaDeFetch = Machine({
  id: "fetch",
  initial: "idle",
  states: {
    idle: {
      // Definimos con 'on' el evento y a donde irá
      on: {
        click: "fetching",
      },
    },
    fetching: {
      // Definimos con 'on' el evento y a donde irá
      on: {
        failure: "error",
        success: "idle",
      },
    },
    error: {
      // Definimos con 'on' el evento y a donde irá
      on: {
        retry: "fetching",
      },
    },
  },
});

Y aquí mostraré como usar la máquina con el ejemplo anterior con un servicio para hacer el fetch dinámico:

import { useMachine } from "@xstate/react";
import { createMachine } from "xstate";

const maquinaDeFetch = createMachine({
  id: "fetch",
  initial: "idle",
  states: {
    idle: {},
    fetching: {},
    error: {},
  },
});

const [state, send] = useMachine(maquinaDeFetch);

Recursos para saber más:

Si te sirvió este post sigamos la conversación por Twitter @Yoshua Díaz.