1. Antes de começar
A incrível inovação do AlphaGo e do AlphaStar demonstrou o potencial de usar o aprendizado de máquina para criar agentes de jogos de nível sobre-humano. É um exercício divertido criar um pequeno jogo com tecnologia de ML para adquirir as habilidades necessárias para criar agentes de jogos poderosos.
Neste codelab, você vai aprender a criar um jogo de tabuleiro usando:
- Agente do TensorFlow para treinar um agente de jogo com aprendizado por reforço
- TensorFlow Serving para disponibilizar o modelo
- Flutter para criar um app de jogo de tabuleiro multiplataforma
Pré-requisitos
- Conhecimento básico de desenvolvimento do Flutter com Dart
- Conhecimento básico de machine learning com o TensorFlow, como treinamento ou implantação
- Conhecimento básico de Python, terminais e Docker
O que você vai aprender
- Como treinar um agente de personagem não jogável (NPC) usando o TensorFlow Agents
- Como disponibilizar o modelo treinado usando o TensorFlow Serving
- Como criar um jogo de tabuleiro multiplataforma com o Flutter
O que é necessário
- SDK do Flutter (em inglês)
- Configuração do Android e do iOS para o Flutter
- Configuração de computador para o Flutter
- Configuração da Web para o Flutter
- Configuração do Visual Studio Code (VS Code) para Flutter e Dart
- Docker
- Bash
- Python 3.7 ou mais recente
2. The Plane Strike Game
O jogo que você vai criar neste codelab se chama "Plane Strike", um pequeno jogo de tabuleiro para dois jogadores que se assemelha ao jogo de tabuleiro "Battleship". As regras são muito simples:
- O jogador humano joga contra um agente NPC treinado por machine learning. O jogador humano pode iniciar o jogo tocando em qualquer célula do tabuleiro do agente.
- No início do jogo, o jogador humano e o agente têm um objeto "avião" (oito células verdes que formam um "avião", como você pode ver no tabuleiro do jogador humano na animação abaixo) nos próprios tabuleiros. Esses "aviões" são colocados aleatoriamente e ficam visíveis apenas para os donos do tabuleiro e ocultos para os oponentes.
- O jogador humano e o agente se revezam para atacar uma célula do tabuleiro um do outro. O jogador humano pode tocar em qualquer célula no tabuleiro do agente, enquanto o agente faz a escolha automaticamente com base na previsão de um modelo de machine learning. A célula tentada fica vermelha se for uma célula "avião" ("acerto"); caso contrário, fica amarela ("erro").
- Quem conseguir oito células vermelhas primeiro vence o jogo, que é reiniciado com novos tabuleiros.
Confira um exemplo de gameplay:

3. Configurar o ambiente de desenvolvimento do Flutter
Para o desenvolvimento em Flutter, você precisa de dois softwares para concluir este codelab: o SDK do Flutter e um editor.
É possível executar o codelab usando qualquer um destes dispositivos:
- O simulador para iOS, que exige a instalação de ferramentas do Xcode.
- O Android Emulator, que requer configuração no Android Studio.
- Um navegador (o Chrome é necessário para depuração).
- Como um aplicativo para computador Windows, Linux ou macOS. Você precisa desenvolver na plataforma em que planeja implantar. Portanto, se quiser desenvolver um app para um computador Windows, você terá que desenvolver no Windows para acessar a cadeia de builds adequada. Há requisitos específicos de cada sistema operacional que são abordados em detalhes em docs.flutter.dev/desktop.
4. Começar a configuração
Para fazer o download do código para este codelab, faça o seguinte:
- Navegue até o repositório do GitHub deste codelab.
- Clique em Code > Download zip para fazer o download de todo o código para este codelab.

- Descompacte o arquivo ZIP baixado para descompactar uma pasta raiz
codelabs-maincom todos os recursos necessários.
Neste codelab, você só precisará dos arquivos no subdiretório tfagents-flutter/ do repositório, que contém várias pastas:
- As pastas de
step0astep6contêm o código inicial baseado em cada etapa deste codelab. - A pasta
finishedcontém o código concluído do app de exemplo finalizado. - Cada pasta contém uma subpasta
backbend, que inclui o código do back-end, e uma subpastafrontend, que inclui o código do front-end do Flutter.
5. Fazer o download das dependências do projeto
Back-end
Abra o terminal e acesse a subpasta tfagents-flutter. Execute o comando a seguir:
pip install -r requirements.txt
Front-end
- No VS Code, clique em File > Open folder e selecione a pasta
step0no código-fonte que você baixou anteriormente. - Abra
step0/frontend/lib/main.dart. Se aparecer uma caixa de diálogo do VS Code solicitando o download dos pacotes necessários para o app inicial, clique em Get packages. - Se a caixa de diálogo não aparecer, abra o terminal e execute o comando
flutter pub getna pastastep0/frontend.

6. Etapa 0: executar o app inicial
- Abra o arquivo
step0/frontend/lib/main.dartno VS Code e verifique se o Android Emulator ou o simulador de iOS está configurado corretamente e aparece na barra de status.
Por exemplo, veja o que você verá ao usar o Pixel 5 com o Android Emulator:

Veja o que você verá ao usar o iPhone 13 com o iOS Simulator:

- Clique em
Iniciar depuração.
Executar e explorar o app
O app será iniciado no Android Emulator ou no iOS Simulator. A interface é bem direta. Há dois tabuleiros de jogo. Um jogador humano pode tocar em qualquer célula do tabuleiro do agente na parte de cima como uma posição de ataque. Você vai treinar um agente inteligente para prever automaticamente onde atacar com base no tabuleiro do jogador humano.
Por baixo dos panos, o app Flutter envia o tabuleiro atual do jogador humano para o back-end, que executa um modelo de aprendizado por reforço e retorna uma posição de célula prevista para o próximo ataque. O front-end vai mostrar o resultado na interface depois de receber a resposta.

Se você clicar em qualquer célula no quadro do agente agora, nada vai acontecer porque o app ainda não consegue se comunicar com o back-end.
7. Etapa 1: criar um ambiente Python do TensorFlow Agents
O objetivo principal deste codelab é projetar um agente que aprende interagindo com um ambiente. Embora o jogo Plane Strike seja relativamente simples e seja possível criar regras para o agente NPC, você usa o aprendizado por reforço para treinar um agente. Assim, você aprende as habilidades e pode criar agentes para outros jogos com facilidade no futuro.
Na configuração padrão de aprendizado por reforço (RL, na sigla em inglês), o agente recebe uma observação a cada etapa de tempo e escolhe uma ação. A ação é aplicada ao ambiente, que retorna uma recompensa e uma nova observação. O agente treina uma política para escolher ações que maximizem a soma das recompensas, também conhecida como retorno. Ao jogar muitas vezes, o agente aprende os padrões e aprimora as habilidades para dominar o jogo. Para formular o jogo Plane Strike como um problema de RL, pense no estado do tabuleiro como a observação, uma posição de ataque como a ação e o sinal de acerto/erro como a recompensa.

Para treinar o agente NPC, você usa o TensorFlow Agents, uma biblioteca de aprendizado por reforço confiável, escalonável e fácil de usar para o TensorFlow.
O TF Agents é ótimo para aprendizado por reforço porque vem com um conjunto extenso de codelabs, exemplos e documentação abrangente para você começar. Você pode usar o TF Agents para resolver problemas de RL realistas e complexos com escalonabilidade e desenvolver novos algoritmos de RL rapidamente. É fácil trocar entre diferentes agentes e algoritmos para experimentação. Ele também é bem testado e fácil de configurar.
Há muitos ambientes de jogos pré-criados implementados no OpenAI Gym (por exemplo, jogos do Atari), no Mujuco etc., que o TF Agents pode usar facilmente. No entanto, como o jogo Plane Strike é totalmente personalizado, primeiro você precisa implementar um novo ambiente do zero.
Para implementar um ambiente Python do TF Agents, você precisa implementar os seguintes 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."""
A mais importante é a função _step(), que recebe uma ação e retorna um novo objeto time_step. No caso do jogo Plane Strike, você tem um tabuleiro. Quando uma nova posição de ataque chega, com base na condição do tabuleiro, o ambiente descobre:
- Como o tabuleiro deve ficar em seguida (a célula deve mudar de cor para vermelho ou amarelo, considerando a localização oculta do avião?)
- Qual recompensa o jogador deve receber por essa posição (recompensa por acerto ou penalidade por erro)?
- O jogo deve terminar? Alguém ganhou?
- Adicione o seguinte código à função
_step()no arquivo_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. Etapa 2: treinar o agente de jogo com o TensorFlow Agents
Com o ambiente do TF Agents configurado, é possível treinar o agente do jogo. Neste codelab, você vai usar um agente REINFORCE. O REINFORCE é um algoritmo de gradiente de política no RL. A ideia básica é ajustar os parâmetros da rede neural da política com base nos indicadores de recompensa coletados durante o jogo para que a rede possa maximizar o retorno em partidas futuras.
- Primeiro, você precisa instanciar os ambientes de treinamento e avaliação. Adicione este código à função
train_agent()no arquivostep2/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)
- Em seguida, crie um agente de aprendizado por reforço que será treinado. Neste codelab, você vai usar o agente REINFORCE, que é baseado em políticas. Adicione este código logo abaixo do código acima:
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 fim, treine o agente no loop de treinamento. No loop, primeiro você coleta alguns episódios de jogadas em um buffer e treina o agente com os dados armazenados em buffer. Adicione este código à função
train_agent()no arquivostep2/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()
- Agora você pode iniciar o treinamento. No terminal, acesse a pasta
step2/backendno computador e execute:
python training.py
O treinamento leva de 8 a 12 horas para ser concluído, dependendo das configurações de hardware. Não é necessário concluir todo o treinamento sozinho, já que um modelo pré-treinado é fornecido em step3. Enquanto isso, você pode monitorar o progresso com o TensorBoard. Abra um novo terminal, acesse a pasta step2/backend no computador e execute:
tensorboard --logdir tf_agents_log/
tf_agents_log é a pasta que contém o registro de treinamento. Confira abaixo um exemplo de execução de treinamento:
É possível notar que a duração média do episódio diminui e o retorno médio aumenta à medida que o treinamento avança. Intuitivamente, é possível entender que, se o agente for mais inteligente e fizer previsões melhores, a duração do jogo será menor e ele vai receber mais recompensas. Isso faz sentido, já que o agente quer terminar o jogo em menos etapas para minimizar o desconto de recompensa pesada nas etapas posteriores.
Após a conclusão do treinamento, o modelo treinado é exportado para a pasta policy_model.
9. Etapa 3: implante o modelo treinado com o TensorFlow Serving
Agora que você treinou o agente de jogo, é possível implantá-lo com o TensorFlow Serving.
- No terminal, acesse a pasta
step3/backendno computador e inicie o TensorFlow Serving com o 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
Primeiro, o Docker faz o download automático da imagem do TensorFlow Serving em um minuto. Depois disso, o TensorFlow Serving será iniciado. O registro será semelhante a este snippet 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 ...
Envie uma solicitação de amostra ao endpoint para garantir que ele esteja funcionando como 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
O endpoint vai retornar uma posição prevista 45, que é (5, 5) no centro do tabuleiro. Para quem tem curiosidade, tente descobrir por que o centro do tabuleiro é uma boa estimativa para a primeira posição de ataque.
{
"predictions": [45]
}
Pronto! Você criou um back-end para prever a próxima posição de ataque do agente NPC.
10. Etapa 4: criar o app Flutter para Android e iOS
O back-end está pronto. Você pode começar a enviar solicitações para recuperar previsões de posição de strike do app Flutter.
- Primeiro, você precisa definir uma classe que encapsule as entradas a serem enviadas. Adicione este código ao arquivo
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;
}
}
Agora é possível enviar a solicitação ao TensorFlow Serving para fazer previsões.
- Adicione este código à função
predict()no arquivostep4/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');
}
Depois que o app recebe a resposta do back-end, você atualiza a interface do jogo para refletir o progresso.
- Adicione este código à função
_gridItemTapped()no arquivostep4/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(() {});
Executar
- Clique em
Start debugging e aguarde o app carregar. - Toque em qualquer célula no quadro do agente para iniciar o jogo.

11. Etapa 5: ativar o app Flutter para plataformas de computador
Além do Android e iOS, o Flutter também oferece suporte a plataformas de computador, incluindo Linux, Mac e Windows.
Linux
- Verifique se o dispositivo de destino está definido como
na barra de status do VSCode. - Clique em
Start debugging e aguarde o app carregar. - Clique em qualquer célula no quadro do agente para iniciar o jogo.

Mac
- Para Mac, você precisa configurar os direitos apropriados porque o app envia solicitações HTTP para o back-end. Consulte Direitos e aplicativos sandbox para mais detalhes.
Adicione este código a step4/frontend/macOS/Runner/DebugProfile.entitlements e step4/frontend/macOS/Runner/Release.entitlements, respectivamente:
<key>com.apple.security.network.client</key>
<true/>
- Verifique se o dispositivo de destino está definido como
na barra de status do VSCode. - Clique em
Start debugging e aguarde o app carregar. - Clique em qualquer célula no quadro do agente para iniciar o jogo.

Windows
- Verifique se o dispositivo de destino está definido como
na barra de status do VSCode. - Clique em
Start debugging e aguarde o app carregar. - Clique em qualquer célula no quadro do agente para iniciar o jogo.

12. Etapa 6: ativar o app Flutter para a plataforma da Web
Outra coisa que você pode fazer é adicionar suporte da Web ao app Flutter. Por padrão, a plataforma da Web é ativada automaticamente para apps do Flutter. Portanto, tudo o que você precisa fazer é iniciá-la.
- Verifique se o dispositivo de destino está definido como
na barra de status do VSCode. - Clique em
Start debugging e aguarde o app carregar no navegador Chrome. - Clique em qualquer célula no quadro do agente para iniciar o jogo.

13. Parabéns
Você criou um app de jogo de tabuleiro com um agente com tecnologia de ML para jogar contra o jogador humano.
