Cómo compilar un juego de mesa con agentes de TensorFlow y Flutter

1. Antes de comenzar

El increíble avance de AlphaGo y AlphaStar demostró el potencial de usar el aprendizaje automático para compilar agentes de juegos de nivel sobrehumano. Es un ejercicio divertido para compilar un pequeño juego potenciado por AA a fin de adquirir las habilidades necesarias para crear agentes de juego potentes.

En este codelab, aprenderás a compilar un juego de mesa usando lo siguiente:

  • Agente de TensorFlow para entrenar a un agente de juego con aprendizaje por refuerzo
  • TensorFlow Serving para entregar el modelo
  • Flutter para crear una app de juegos de mesa multiplataforma

Requisitos previos

  • Conocimientos básicos sobre el desarrollo de Flutter con Dart
  • Conocimientos básicos del aprendizaje automático con TensorFlow, como el entrenamiento y la implementación
  • Conocimientos básicos de Python, terminales y Docker

Qué aprenderás

  • Cómo entrenar a un agente de personaje que no es un jugador (NPC) con agentes de TensorFlow
  • Cómo entregar el modelo entrenado con TensorFlow Serving
  • Cómo crear un juego de mesa multiplataforma de Flutter

Requisitos

2. Juego de Plane Strike

El juego que compilarás en este codelab se llama "Plane Strike", un pequeño juego de mesa para 2 jugadores similar al juego de mesa "Battleship". Las reglas son muy simples:

  • El jugador juega contra un agente de NPC entrenado con aprendizaje automático. El jugador puede iniciar el juego presionando cualquier celda en el tablero del agente.
  • Al comienzo del juego, el jugador humano y el agente tienen un "avión" (8 celdas verdes que forman un "plano", como se puede ver en el tablero del jugador humano en la siguiente animación) en sus propios tableros. estos "aviones" se colocan al azar, solo visibles para los propietarios del tablero y escondidas para sus oponentes.
  • El jugador humano y el agente se turnan para golpear una celda del tablero del otro. El jugador puede presionar cualquier celda en el tablero del agente, mientras que el agente tomará la decisión automáticamente en función de la predicción de un modelo de aprendizaje automático. La celda que intentas hacer se vuelve roja si es un "avión". celda ("hit") de lo contrario, se vuelve amarillo ("miss").
  • Quien consiga 8 celdas rojas primero gana el juego; y, luego, el juego se reinicia con tableros nuevos.

Este es un ejemplo del juego:

77cead530c5a4aff.gif

3. Configura tu entorno de desarrollo de Flutter

Para el desarrollo de Flutter, necesitas dos tipos de software para completar este codelab: el SDK de Flutter y un editor.

Puedes ejecutar el codelab con cualquiera de estos dispositivos o modalidades:

  • El simulador de iOS (requiere instalar las herramientas de Xcode)
  • Android Emulator (requiere configuración en Android Studio)
  • Un navegador (se requiere Chrome para la depuración)
  • Como una aplicación para computadoras que ejecuten Windows, Linux o macOS (debes desarrollarla en la plataforma donde tengas pensado realizar la implementación; por lo tanto, si quieres desarrollar una app de escritorio para Windows, debes desarrollarla en ese SO a fin de obtener acceso a la cadena de compilación correcta; encuentra detalles sobre los requisitos específicos del sistema operativo en docs.flutter.dev/desktop).

4. Prepárate

Para descargar el código de este codelab, haz lo siguiente:

  1. Navega al repositorio de GitHub de este codelab.
  2. Haz clic en Code > Download ZIP para descargar todo el código de este codelab.

2cd45599f51fb8a2.png

  1. Descomprime el archivo ZIP descargado para desempaquetar una carpeta raíz codelabs-main con todos los recursos que necesitas.

Para este codelab, solo necesitas los archivos del subdirectorio tfagents-flutter/ del repositorio, que contiene varias carpetas:

  • Las carpetas step0 a step6 contienen el código de partida en el que se basa cada paso de este codelab.
  • La carpeta finished contiene el código completado de la app de ejemplo finalizada.
  • Cada carpeta contiene una subcarpeta backbend, que incluye el código de backend y una subcarpeta frontend, que incluye el código del frontend de Flutter.

5. Descarga las dependencias del proyecto

Backend

Abre la terminal y ve a la subcarpeta tfagents-flutter. Ejecuta lo siguiente:

pip install -r requirements.txt

Frontend

  1. En VS Code, haz clic en File > Abre la carpeta y, luego, selecciona la carpeta step0 del código fuente que descargaste antes.
  2. Abre el archivo step0/frontend/lib/main.dart. Si aparece un diálogo de VS Code en el que se te solicita que descargues los paquetes necesarios para la app de partida, haz clic en Get packages.
  3. Si no ves este diálogo, abre la terminal y, luego, ejecuta el comando flutter pub get en la carpeta step0/frontend.

7ada07c300f166a6.png

6. Paso 0: Ejecuta la app de partida

  1. Abre el archivo step0/frontend/lib/main.dart en VS Code y asegúrate de que Android Emulator o el simulador de iOS estén configurados correctamente y aparezcan en la barra de estado.

Por ejemplo, a continuación, se muestra lo que ves cuando usas un Pixel 5 con Android Emulator:

9767649231898791.png

A continuación, se muestra lo que ves cuando usas un iPhone 13 con el simulador de iOS:

95529e3a682268b2.png

  1. Haz clic en a19a0c68bc4046e6.png Start debugging.

Ejecuta y explora la app

La app debe iniciarse en Android Emulator o el simulador de iOS. La IU es bastante sencilla. Hay 2 tableros de juego. un jugador puede presionar en la parte superior de cualquier celda del tablero del agente como una posición de golpe. Entrenarás a un agente inteligente para que prediga automáticamente dónde golpear según el tablero del jugador humano.

De forma interna, la app de Flutter enviará el tablero actual del jugador humano al backend, que ejecuta un modelo de aprendizaje por refuerzo y muestra una posición prevista para la celda a continuación. El frontend mostrará el resultado en la IU después de recibir la respuesta.

838a48a331e8a.png 15cba2e741149c95.png

Si haces clic en cualquier celda del panel del agente ahora, no sucederá nada porque la app aún no puede comunicarse con el backend.

7. Paso 1: Crea un entorno de Python para agentes de TensorFlow

El objetivo principal de este codelab es diseñar un agente que aprenda interactuando con un entorno. Si bien el juego de Plane Strike es relativamente simple y es posible elaborar reglas para el agente de NPC, puedes usar el aprendizaje por refuerzo para entrenar a un agente, de modo que aprendas las habilidades y puedas crear agentes fácilmente para otros juegos en el futuro.

En la configuración estándar de aprendizaje por refuerzo (RL), el agente recibe una observación en cada paso y elige una acción. La acción se aplica al entorno y este devuelve una recompensa y una nueva observación. El agente entrena una política para elegir acciones a fin de maximizar la suma de recompensas, también conocida como devolución. Al jugar muchas veces el juego, el agente puede aprender los patrones y perfeccionar sus habilidades para dominar el juego. Para formular el juego de Plane Strike como un problema de RL, piensa en el estado del tablero como la observación, la posición de una huelga como la acción y la señal de golpe/error como recompensa.

bc5da07bc45062f4.png

Para entrenar al agente de NPC, debes aprovechar TensorFlow Agents, una biblioteca de aprendizaje por refuerzo confiable, escalable y fácil de usar para TensorFlow.

TF Agents es excelente para el aprendizaje por refuerzo porque cuenta con un amplio conjunto de codelabs, ejemplos y una amplia documentación para que puedas comenzar. Puedes usar TF Agents para resolver problemas de RL realistas y complejos con escalabilidad y desarrollar nuevos algoritmos de RL con rapidez. Puedes intercambiar con facilidad entre distintos agentes y algoritmos para la experimentación. También está bien probado y es fácil de configurar.

Hay muchos entornos de juego compilados previamente que se implementaron en el Gym de OpenAI (p.ej., juegos de Atari), Mujuco, etc., que los agentes de TF pueden aprovechar fácilmente. Pero como el juego Plane Strike es un juego completamente personalizado, primero debes implementar un nuevo entorno desde cero.

Para implementar un entorno de Python de TF Agents, debes implementar los siguientes métodos:

class YourGameEnv(py_environment.PyEnvironment):

  def __init__(self):
    """Initialize environment."""


  def action_spec(self):
    """Return action_spec."""


  def observation_spec(self):
    """Return observation_spec."""


  def _reset(self):
    """Return initial_time_step."""


  def _step(self, action):
    """Apply action and return new time_step."""

La más importante es la función _step(), que realiza una acción y muestra un nuevo objeto time_step. En el caso del juego Plane Strike, tienes un tablero de juego; Cuando aparece una nueva posición de falta, en función de la condición del tablero de juego, el entorno descubre lo siguiente:

  • Cómo debería verse el tablero de juego a continuación (¿la celda debería cambiar de color a rojo o amarillo según la ubicación del plano oculto?)
  • ¿Qué recompensa debe recibir el jugador por esa posición (recompensa por golpe o penalización fallida?)
  • ¿El juego debería terminar? (¿Alguien ganó?)
  • Agrega el siguiente código a la función _step() en el archivo _planestrike_py_environment.py:
if self._hit_count == self._plane_size:
    self._episode_ended = True
    return self.reset()

if self._strike_count + 1 == self._max_steps:
    self.reset()
    return ts.termination(
        np.array(self._visible_board, dtype=np.float32), UNFINISHED_GAME_REWARD
    )

self._strike_count += 1
action_x = action // self._board_size
action_y = action % self._board_size
# Hit
if self._hidden_board[action_x][action_y] == HIDDEN_BOARD_CELL_OCCUPIED:
    # Non-repeat move
    if self._visible_board[action_x][action_y] == VISIBLE_BOARD_CELL_UNTRIED:
        self._hit_count += 1
        self._visible_board[action_x][action_y] = VISIBLE_BOARD_CELL_HIT
        # Successful strike
        if self._hit_count == self._plane_size:
            # Game finished
            self._episode_ended = True
            return ts.termination(
                np.array(self._visible_board, dtype=np.float32),
                FINISHED_GAME_REWARD,
            )
        else:
            self._episode_ended = False
            return ts.transition(
                np.array(self._visible_board, dtype=np.float32),
                HIT_REWARD,
                self._discount,
            )
    # Repeat strike
    else:
        self._episode_ended = False
        return ts.transition(
            np.array(self._visible_board, dtype=np.float32),
            REPEAT_STRIKE_REWARD,
            self._discount,
        )
# Miss
else:
    # Unsuccessful strike
    self._episode_ended = False
    self._visible_board[action_x][action_y] = VISIBLE_BOARD_CELL_MISS
    return ts.transition(
        np.array(self._visible_board, dtype=np.float32),
        MISS_REWARD,
        self._discount,

8. Paso 2: Entrena al agente de juego con agentes de TensorFlow

Con el entorno de agentes de TF implementado, puedes entrenar al agente de juego. En este codelab, usarás un agente de REFUERZO. REINFORCE es un algoritmo de gradiente de políticas en RL. Su idea básica es ajustar los parámetros de la red neuronal de política en función de los indicadores de recompensa recopilados durante el juego, de modo que la red de políticas pueda maximizar el retorno en futuras jugadas.

  • Primero, debes crear una instancia de los entornos de entrenamiento y evaluación. Agrega este código a la función train_agent() en el archivo step2/backend/training.py:
train_py_env = planestrike_py_environment.PlaneStrikePyEnvironment(
    board_size=BOARD_SIZE, discount=DISCOUNT, max_steps=BOARD_SIZE**2
)
eval_py_env = planestrike_py_environment.PlaneStrikePyEnvironment(
    board_size=BOARD_SIZE, discount=DISCOUNT, max_steps=BOARD_SIZE**2
)

train_env = tf_py_environment.TFPyEnvironment(train_py_env)
eval_env = tf_py_environment.TFPyEnvironment(eval_py_env)
  • A continuación, debes crear un agente de aprendizaje por refuerzo que se entrenará. En este codelab, usarás el agente REFUERZO, que es un agente basado en políticas. Agrega este código justo debajo del código anterior:
actor_net = tfa.networks.Sequential(
    [
        tfa.keras_layers.InnerReshape([BOARD_SIZE, BOARD_SIZE], [BOARD_SIZE**2]),
        tf.keras.layers.Dense(FC_LAYER_PARAMS, activation="relu"),
        tf.keras.layers.Dense(BOARD_SIZE**2),
        tf.keras.layers.Lambda(lambda t: tfp.distributions.Categorical(logits=t)),
    ],
    input_spec=train_py_env.observation_spec(),
)

optimizer = tf.keras.optimizers.Adam(learning_rate=LEARNING_RATE)

train_step_counter = tf.Variable(0)

tf_agent = reinforce_agent.ReinforceAgent(
    train_env.time_step_spec(),
    train_env.action_spec(),
    actor_network=actor_net,
    optimizer=optimizer,
    normalize_returns=True,
    train_step_counter=train_step_counter,
)
  • Por último, entrena al agente en el ciclo de entrenamiento. En el bucle, primero se recopilan algunos episodios de jugadas en un búfer y, luego, se entrena al agente con los datos almacenados en el búfer. Agrega este código a la función train_agent() en el archivo step2/backend/training.py:
# Collect a few episodes using collect_policy and save to the replay buffer.
collect_episode(
    train_py_env,
    collect_policy,
    COLLECT_EPISODES_PER_ITERATION,
    replay_buffer_observer,
)

# Use data from the buffer and update the agent's network.
iterator = iter(replay_buffer.as_dataset(sample_batch_size=1))
trajectories, _ = next(iterator)
tf_agent.train(experience=trajectories)
replay_buffer.clear()
  • Ahora puedes iniciar la capacitación. En tu terminal, ve a la carpeta step2/backend de tu computadora y ejecuta lo siguiente:
python training.py

El entrenamiento tarda entre 8 y 12 horas en completarse, según la configuración de hardware (no es necesario que termines todo el entrenamiento por tu cuenta, ya que en step3 se proporciona un modelo previamente entrenado). Mientras tanto, puedes supervisar el progreso con TensorBoard. Abre una terminal nueva, ve a la carpeta step2/backend en tu computadora y ejecuta lo siguiente:

tensorboard --logdir tf_agents_log/

tf_agents_log es la carpeta que contiene el registro de entrenamiento. A continuación, se muestra un ejemplo de una ejecución de entrenamiento:

33e12e2b387c063a.png 8488632ccf43348a.png

Puedes ver que la duración promedio de los episodios disminuye y el retorno promedio aumenta a medida que avanza el entrenamiento. De manera intuitiva, puedes comprender que si el agente es más inteligente y hace mejores predicciones, la duración del juego se reduce y el agente junta más recompensas. Esto tiene sentido, ya que el agente quiere terminar el juego en menos pasos para minimizar los grandes descuentos en recompensas en los pasos posteriores.

Cuando finaliza el entrenamiento, el modelo entrenado se exporta a la carpeta policy_model.

9. Paso 3: Implementa el modelo entrenado con TensorFlow Serving

Ahora que entrenaste el agente del juego, puedes implementarlo con TensorFlow Serving.

  • En tu terminal, ve a la carpeta step3/backend en tu computadora y, luego, inicia TensorFlow Serving con Docker:
docker run -t --rm -p 8501:8501 -p 8500:8500 -v "$(pwd)/backend/policy_model:/models/policy_model" -e MODEL_NAME=policy_model tensorflow/serving

Docker descarga automáticamente la imagen de TensorFlow Serving primero, lo cual tarda un minuto. Luego, TensorFlow Serving debería iniciarse. El registro debería verse como este fragmento de código:

2022-05-30 02:38:54.147771: I tensorflow_serving/model_servers/server.cc:89] Building single TensorFlow model file config:  model_name: policy_model model_base_path: /models/policy_model
2022-05-30 02:38:54.148222: I tensorflow_serving/model_servers/server_core.cc:465] Adding/updating models.
2022-05-30 02:38:54.148273: I tensorflow_serving/model_servers/server_core.cc:591]  (Re-)adding model: policy_model
2022-05-30 02:38:54.262684: I tensorflow_serving/core/basic_manager.cc:740] Successfully reserved resources to load servable {name: policy_model version: 123}
2022-05-30 02:38:54.262768: I tensorflow_serving/core/loader_harness.cc:66] Approving load for servable version {name: policy_model version: 123}
2022-05-30 02:38:54.262787: I tensorflow_serving/core/loader_harness.cc:74] Loading servable version {name: policy_model version: 123}
2022-05-30 02:38:54.265010: I external/org_tensorflow/tensorflow/cc/saved_model/reader.cc:38] Reading SavedModel from: /models/policy_model/123
2022-05-30 02:38:54.277811: I external/org_tensorflow/tensorflow/cc/saved_model/reader.cc:90] Reading meta graph with tags { serve }
2022-05-30 02:38:54.278116: I external/org_tensorflow/tensorflow/cc/saved_model/reader.cc:132] Reading SavedModel debug info (if present) from: /models/policy_model/123
2022-05-30 02:38:54.280229: I external/org_tensorflow/tensorflow/core/platform/cpu_feature_guard.cc:142] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations:  AVX2 FMA
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
2022-05-30 02:38:54.332352: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:206] Restoring SavedModel bundle.
2022-05-30 02:38:54.337000: I external/org_tensorflow/tensorflow/core/platform/profile_utils/cpu_utils.cc:114] CPU Frequency: 2193480000 Hz
2022-05-30 02:38:54.402803: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:190] Running initialization op on SavedModel bundle at path: /models/policy_model/123
2022-05-30 02:38:54.410707: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:277] SavedModel load for tags { serve }; Status: success: OK. Took 145695 microseconds.
2022-05-30 02:38:54.412726: I tensorflow_serving/servables/tensorflow/saved_model_warmup_util.cc:59] No warmup data file found at /models/policy_model/123/assets.extra/tf_serving_warmup_requests
2022-05-30 02:38:54.417277: I tensorflow_serving/core/loader_harness.cc:87] Successfully loaded servable version {name: policy_model version: 123}
2022-05-30 02:38:54.419846: I tensorflow_serving/model_servers/server_core.cc:486] Finished adding/updating models
2022-05-30 02:38:54.420066: I tensorflow_serving/model_servers/server.cc:367] Profiler service is enabled
2022-05-30 02:38:54.428339: I tensorflow_serving/model_servers/server.cc:393] Running gRPC ModelServer at 0.0.0.0:8500 ...
[warn] getaddrinfo: address family for nodename not supported
2022-05-30 02:38:54.431620: I tensorflow_serving/model_servers/server.cc:414] Exporting HTTP/REST API at:localhost:8501 ...
[evhttp_server.cc : 245] NET_LOG: Entering the event loop ...

Puedes enviar una solicitud de muestra al extremo para asegurarte de que funcione según lo esperado:

curl -d '{"signature_name":"action","instances":[{"0/discount":0.0,"0/observation":[[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,     0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]],"0/reward":0.0,"0/step_type":0}]}'     -X POST http://localhost:8501/v1/models/policy_model:predict

El extremo mostrará una posición predicha 45, que es (5, 5) en el centro del tablero (para los curiosos, puedes intentar averiguar por qué el centro del tablero es una buena suposición para la primera posición de golpe).

{
    "predictions": [45]
}

Eso es todo. Compilaste correctamente un backend para predecir la siguiente posición de falta del agente de NPC.

10. Paso 4: Crea la app de Flutter para iOS y Android

El backend está listo. Puedes comenzar a enviarle solicitudes para recuperar predicciones de posición de faltas desde la app de Flutter.

  • Primero, debes definir una clase que una las entradas que se enviarán. Agrega este código al archivo step4/frontend/lib/game_agent.dart:
class Inputs {
  final List<double> _boardState;
  Inputs(this._boardState);

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = <String, dynamic>{};
    data['0/discount'] = [0.0];
    data['0/observation'] = [_boardState];
    data['0/reward'] = [0.0];
    data['0/step_type'] = [0];
    return data;
  }
}

Ahora puedes enviar la solicitud a TensorFlow Serving para hacer predicciones.

  • Agrega este código a la función predict() en el archivo step4/frontend/lib/game_agent.dart:
var flattenedBoardState = boardState.expand((i) => i).toList();
final response = await http.post(
  Uri.parse('http://$server:8501/v1/models/policy_model:predict'),
  body: jsonEncode(<String, dynamic>{
    'signature_name': 'action',
    'instances': [Inputs(flattenedBoardState)]
  }),
);

if (response.statusCode == 200) {
  var output = List<int>.from(
      jsonDecode(response.body)['predictions'] as List<dynamic>);
  return output[0];
} else {
  throw Exception('Error response');
}

Una vez que la app recibe la respuesta del backend, debes actualizar la IU del juego para que refleje el progreso.

  • Agrega este código a la función _gridItemTapped() en el archivo step4/frontend/lib/main.dart:
int agentAction =
    await _policyGradientAgent.predict(_playerVisibleBoardState);
_agentActionX = agentAction ~/ _boardSize;
_agentActionY = agentAction % _boardSize;
if (_playerHiddenBoardState[_agentActionX][_agentActionY] ==
    hiddenBoardCellOccupied) {
  // Non-repeat move
  if (_playerVisibleBoardState[_agentActionX][_agentActionY] ==
      visibleBoardCellUntried) {
    _agentHitCount++;
  }
  _playerVisibleBoardState[_agentActionX][_agentActionY] =
      visibleBoardCellHit;
} else {
  _playerVisibleBoardState[_agentActionX][_agentActionY] =
      visibleBoardCellMiss;
}
setState(() {});

Ejecuta la app

  1. Haz clic en a19a0c68bc4046e6.png Start debugging y espera a que se cargue la app.
  2. Presiona cualquier celda en el tablero del agente para iniciar el juego.

852942d0de299c1f.png 6ae3601470c8e33a.png

11. Paso 5: Habilita la app de Flutter para las plataformas de escritorio

Además de iOS y Android, Flutter también admite plataformas de escritorio, como Linux, Mac y Windows.

Linux

  1. Asegúrate de que el dispositivo de destino esté configurado como 86cba523de82b4f9.png en la barra de estado de VSCode.
  2. Haz clic en a19a0c68bc4046e6.png Start debugging y espera a que se cargue la app.
  3. Haz clic en cualquier celda del tablero del agente para iniciar el juego.

48594c7c0a589733.png

Mac

  1. En Mac, deberás configurar los derechos adecuados, ya que la app enviará solicitudes HTTP al backend. Para obtener más información, consulta Derechos y zona de pruebas de apps.

Agrega este código a step4/frontend/macOS/Runner/DebugProfile.entitlements y step4/frontend/macOS/Runner/Release.entitlements respectivamente:

<key>com.apple.security.network.client</key>
<true/>
  1. Asegúrate de que el dispositivo de destino esté configurado como eb4b0b5563824138.png en la barra de estado de VSCode.
  2. Haz clic en a19a0c68bc4046e6.png Start debugging y espera a que se cargue la app.
  3. Haz clic en cualquier celda del tablero del agente para iniciar el juego.

55a5de3674194e89.png

Windows

  1. Asegúrate de que el dispositivo de destino esté configurado como 9587be1bb375bc0f.png en la barra de estado de VSCode.
  2. Haz clic en a19a0c68bc4046e6.png Start debugging y espera a que se cargue la app.
  3. Haz clic en cualquier celda del tablero del agente para iniciar el juego.

41d9f87d84c5e755.png

12. Paso 6: Habilita la app de Flutter para la plataforma web

Algo más que puedes hacer es agregar compatibilidad web a la app de Flutter. De forma predeterminada, la plataforma web se habilita automáticamente para las apps de Flutter, por lo que solo debes iniciarla.

  1. Asegúrate de que el dispositivo de destino esté configurado como 71db93efa928d15d.png en la barra de estado de VSCode.
  2. Haz clic en a19a0c68bc4046e6.png Start debugging y espera a que la app se cargue en el navegador Chrome.
  3. Haz clic en cualquier celda del tablero del agente para iniciar el juego.

fae7490304e28dfe.png

13. Felicitaciones

Creaste una aplicación de juego de mesa con un agente con tecnología de AA para jugar contra los jugadores.

Más información