1. Antes de comenzar
El increíble avance de AlphaGo y AlphaStar demostró el potencial del uso del aprendizaje automático para crear agentes de juego con un nivel sobrehumano. Es un ejercicio divertido crear un pequeño juego potenciado por AA para adquirir las habilidades necesarias para crear agentes de juego potentes.
En este codelab, aprenderás a compilar un juego de mesa con lo siguiente:
- Agente de TensorFlow para entrenar un agente de juegos con aprendizaje por refuerzo
- TensorFlow Serving para derivar 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 un agente de personaje no jugador (PNJ) con TensorFlow Agents
- Cómo entregar el modelo entrenado con TensorFlow Serving
- Cómo compilar un juego de mesa multiplataforma con Flutter
Requisitos
- SDK de Flutter
- Configuración de Android o iOS para Flutter
- Configuración de Flutter para computadoras
- Configuración de Flutter para la Web
- Configuración de Visual Studio Code (VS Code) para Flutter y Dart
- Docker
- Bash
- Python 3.7 o versiones posteriores
2. The Plane Strike Game
El juego que compilarás en este codelab se llama "Ataque aéreo", un pequeño juego de mesa para 2 jugadores que se asemeja al juego de mesa "Batalla naval". Las reglas son muy simples:
- El jugador humano juega contra un agente de PNJ entrenado por aprendizaje automático. El jugador humano puede iniciar el juego presionando cualquier celda del tablero del agente.
- Al comienzo del juego, el jugador humano y el agente tienen un objeto "avión" (8 celdas verdes que forman un "avión", como se puede ver en el tablero del jugador humano en la siguiente animación) en sus respectivos tableros. Estos "aviones" se colocan de forma aleatoria y solo son visibles para los propietarios del tablero, y están ocultos para sus oponentes.
- El jugador humano y el agente se turnan para atacar una celda del tablero del otro. El jugador humano puede presionar cualquier celda del tablero del agente, mientras que el agente tomará la decisión automáticamente según la predicción de un modelo de aprendizaje automático. La celda intentada se vuelve roja si es una celda de "avión" ("acierto"); de lo contrario, se vuelve amarilla ("falla").
- Quien logre 8 celdas rojas primero gana el juego, que se reinicia con tableros nuevos.
A continuación, se muestra un ejemplo de la jugabilidad del juego:

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:
- Navega al repositorio de GitHub de este codelab.
- Haz clic en Code > Download ZIP para descargar todo el código de este codelab.

- Descomprime el archivo ZIP descargado para desempaquetar una carpeta raíz
codelabs-maincon todos los recursos que necesitas.
Para este codelab, solo necesitas los archivos del subdirectorio tfagents-flutter/ en el repositorio, que contiene varias carpetas:
- Las carpetas
step0astep6, que contienen el código de partida en el que se basa cada paso de este codelab. - La carpeta
finishedcontiene 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 subcarpetafrontend, que incluye el código de 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
- En VS Code, haz clic en File > Open folder y, luego, selecciona la carpeta
step0del código fuente que descargaste antes. - 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. - Si no ves este diálogo, abre la terminal y, luego, ejecuta el comando
flutter pub geten la carpetastep0/frontend.

6. Paso 0: Ejecuta la app de inicio
- Abre el archivo
step0/frontend/lib/main.darten 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:

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

- Haz clic en
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 humano puede presionar cualquier celda del tablero del agente en la parte superior como posición de ataque. Entrenarás a un agente inteligente para que prediga automáticamente dónde golpear según el tablero del jugador humano.
En segundo plano, la app de Flutter enviará el tablero actual del jugador humano al backend, que ejecuta un modelo de aprendizaje por refuerzo y devuelve una posición de celda predicha para el próximo golpe. El frontend mostrará el resultado en la IU después de recibir la respuesta.

Si ahora haces clic en cualquier celda del tablero del agente, no sucederá nada porque la app aún no puede comunicarse con el backend.
7. Paso 1: Crea un entorno de Python de TensorFlow Agents
El objetivo principal de este codelab es diseñar un agente que aprenda interactuando con un entorno. Si bien el juego Plane Strike es relativamente simple y es posible crear reglas de forma manual para el agente PNJ, usarás el aprendizaje por refuerzo para entrenar a un agente y, así, adquirir las habilidades necesarias para crear agentes para otros juegos en el futuro.
En el entorno estándar del 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 que maximicen la suma de recompensas, también conocida como retorno. Al jugar muchas veces, el agente puede aprender los patrones y perfeccionar sus habilidades para dominar el juego. Para formular el juego Plane Strike como un problema de RL, considera el estado del tablero como la observación, la posición de ataque como la acción y el indicador de acierto o falla como la recompensa.

Para entrenar al agente de PNJ, aprovechas TensorFlow Agents, que es una biblioteca de aprendizaje por refuerzo confiable, escalable y fácil de usar para TensorFlow.
TF Agents es ideal para el aprendizaje por refuerzo porque incluye un amplio conjunto de codelabs, ejemplos y documentación detallada para que comiences. Puedes usar TF-Agents para resolver problemas de RL realistas y complejos con escalabilidad, y desarrollar nuevos algoritmos de RL rápidamente. Puedes intercambiar fácilmente entre diferentes agentes y algoritmos para experimentar. También se probó bien y es fácil de configurar.
Hay muchos entornos de juegos prediseñados implementados en OpenAI Gym (p.ej., juegos de Atari), Mujuco, etc., que TF Agents puede aprovechar fácilmente. Sin embargo, dado que el juego Plane Strike es un juego personalizado completo, 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 toma una acción y devuelve un nuevo objeto time_step. En el caso del juego Plane Strike, tienes un tablero de juego. Cuando llega una nueva posición de ataque, según la condición del tablero de juego, el entorno determina lo siguiente:
- Cómo debería verse el tablero de juego a continuación (¿debería la celda cambiar su color a rojo o amarillo, dada la ubicación oculta del avión?)
- ¿Qué recompensa debería recibir el jugador por esa posición (recompensa por acierto o penalización por error)?
- ¿Debería finalizar el juego? (¿Ganó alguien?)
- Agrega el siguiente código a la función
_step()del 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 juegos con TensorFlow Agents
Con el entorno de TF-Agents implementado, puedes entrenar al agente del juego. En este codelab, usarás un agente de REINFORCE. 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 la política en función de los indicadores de recompensa recopilados durante el juego, de modo que la red de la política pueda maximizar el retorno en partidas futuras.
- Primero, debes crear instancias de los entornos de entrenamiento y evaluación. Agrega este código a la función
train_agent()en el archivostep2/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 REINFORCE, 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 bucle de entrenamiento. En el bucle, primero recopilas algunos episodios de partidas en un búfer y, luego, entrenas al agente con los datos almacenados en el búfer. Agrega este código a la función
train_agent()en el archivostep2/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 el entrenamiento. En tu terminal, ve a la carpeta
step2/backendde tu computadora y ejecuta el siguiente comando:
python training.py
El entrenamiento tarda entre 8 y 12 horas en completarse, según la configuración de hardware (no es necesario que completes todo el entrenamiento por tu cuenta, ya que se proporciona un modelo previamente entrenado en step3). Mientras tanto, puedes supervisar el progreso con TensorBoard. Abre una terminal nueva, ve a la carpeta step2/backend en tu computadora y ejecuta el siguiente comando:
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 ejecución del entrenamiento:
Puedes ver que la duración promedio de los episodios disminuye y el retorno promedio aumenta a medida que avanza el entrenamiento. Intuitivamente, puedes comprender que, si el agente es más inteligente y hace mejores predicciones, la duración del juego se acorta y el agente obtiene más recompensas. Esto tiene sentido, ya que el agente quiere terminar el juego en menos pasos para minimizar el gran descuento de la recompensa en los pasos posteriores.
Una vez que se completa 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 de juegos, puedes implementarlo con TensorFlow Serving.
- En tu terminal, ve a la carpeta
step3/backenden 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 previsto:
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 de la API devolverá una posición predicha 45, que es (5, 5) en el centro del tablero (si tienes curiosidad, 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. Creaste correctamente un backend para predecir la próxima posición de golpe del agente PNJ.
10. Paso 4: Crea la app de Flutter para Android y iOS
El backend está listo. Puedes comenzar a enviarle solicitudes para recuperar predicciones de la posición de golpe desde la app de Flutter.
- Primero, debes definir una clase que encapsule 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 realizar predicciones.
- Agrega este código a la función
predict()en el archivostep4/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, actualizas la IU del juego para reflejar el progreso.
- Agrega este código a la función
_gridItemTapped()en el archivostep4/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(() {});
Ejecución
- Haz clic en
Start debugging y espera a que se cargue la app. - Presiona cualquier celda del tablero del agente para comenzar el juego.

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 computadoras, como Linux, macOS y Windows.
Linux
- Asegúrate de que el dispositivo de destino esté configurado como
en la barra de estado de VS Code. - Haz clic en
Start debugging y espera a que se cargue la app. - Haz clic en cualquier celda del tablero del agente para comenzar el juego.

Mac
- En Mac, debes 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/>
- Asegúrate de que el dispositivo de destino esté configurado como
en la barra de estado de VS Code. - Haz clic en
Start debugging y espera a que se cargue la app. - Haz clic en cualquier celda del tablero del agente para comenzar el juego.

Windows
- Asegúrate de que el dispositivo de destino esté configurado como
en la barra de estado de VS Code. - Haz clic en
Start debugging y espera a que se cargue la app. - Haz clic en cualquier celda del tablero del agente para comenzar el juego.

12. Paso 6: Habilita la app de Flutter para la plataforma web
Otra cosa 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.
- Asegúrate de que el dispositivo de destino esté configurado como
en la barra de estado de VS Code. - Haz clic en
Start debugging y espera a que la app se cargue en el navegador Chrome. - Haz clic en cualquier celda del tablero del agente para comenzar el juego.

13. Felicitaciones
Creaste una app de juego de mesa con un agente potenciado por AA para jugar contra el jugador humano.
