1. 시작하기 전에
AlphaGo와 AlphaStar의 놀라운 발전은 머신러닝을 사용해 초인간 수준의 게임 에이전트를 빌드할 수 있는 잠재력을 보여주었습니다. 소규모 ML 기반 게임을 빌드하여 강력한 게임 에이전트를 만드는 데 필요한 기술을 익히는 것은 재미있는 연습입니다.
이 Codelab에서는 다음을 사용하여 보드 게임을 빌드하는 방법을 알아봅니다.
- 강화 학습으로 게임 에이전트를 학습시키는 TensorFlow 에이전트
- TensorFlow Serving으로 모델 제공
- Flutter로 크로스 플랫폼 보드게임 앱 만들기
기본 요건
- Flutter 개발 및 Dart에 관한 기본 지식
- 학습 및 배포와 같은 TensorFlow를 활용한 머신러닝에 관한 기본 지식
- Python, 터미널, Docker에 관한 기본 지식
학습할 내용
- TensorFlow 에이전트를 사용하여 NPC (플레이어 외 캐릭터) 에이전트를 학습시키는 방법
- TensorFlow Serving을 사용하여 학습된 모델을 서빙하는 방법
- 크로스 플랫폼 Flutter 보드게임 빌드 방법
필요한 항목
- Flutter SDK
- Flutter용 Android 및 iOS 설정
- Flutter용 데스크톱 설정
- Flutter용 웹 설정
- Flutter 및 Dart용 Visual Studio Code(VS Code) 설정
- Docker
- Bash
- Python 3.7 이상
2. 비행기 스트라이크 게임
이 Codelab에서 빌드하는 게임의 이름은 'Plane Strike'로, 보드 게임 'Battleship'과 비슷한 작은 2인용 보드게임입니다. 규칙은 매우 간단합니다.
- 플레이어가 머신러닝으로 훈련된 NPC 요원과 대결합니다. 플레이어는 에이전트 보드에서 아무 셀이나 탭하여 게임을 시작할 수 있습니다.
- 게임 시작 시 플레이어와 에이전트는 각각 '비행기'를 갖고 있습니다. 객체 (아래 애니메이션에서 플레이어의 보드에서 볼 수 있듯이 '평면'을 형성하는 8개의 녹색 셀)를 자체 보드에 추가합니다. 이 '비행기'는 무작위로 배치되어 보드 소유자에게만 표시되고 상대에게는 숨겨집니다.
- 인간 플레이어와 에이전트가 번갈아 가면서 서로의 보드의 한 셀에 타격합니다. 플레이어는 에이전트 보드에서 아무 셀이나 탭할 수 있으며, 에이전트는 머신러닝 모델의 예측에 따라 자동으로 선택합니다. 시도한 셀이 '평면'인 경우 빨간색으로 바뀝니다. 셀('hit') 노란색으로 표시됩니다('miss').
- 적색 셀 8개를 달성하면 승자가 됩니다. 새로운 보드로 게임이 다시 시작됩니다.
다음은 게임의 샘플 게임플레이입니다.
3. Flutter 개발 환경 설정
Flutter 개발의 경우 이 Codelab을 완료하려면 Flutter SDK와 편집기라는 두 가지 소프트웨어가 필요합니다.
다음 기기 중 하나를 사용하여 이 Codelab을 실행할 수 있습니다.
- iOS 시뮬레이터(Xcode 도구 설치 필요)
- Android Emulator(Android 스튜디오 설정 필요)
- 브라우저(디버깅 시 Chrome 필요)
- Windows, Linux 또는 macOS 데스크톱 애플리케이션. 배포에 사용할 플랫폼에서 개발해야 합니다. 따라서 Windows 데스크톱 앱을 개발하려면 적절한 빌드 체인에 액세스할 수 있도록 Windows에서 개발해야 합니다. docs.flutter.dev/desktop에 운영체제별 요구사항이 자세히 설명되어 있습니다.
4. 설정
이 Codelab의 코드를 다운로드하려면 다음 안내를 따르세요.
- 이 Codelab의 GitHub 저장소로 이동합니다.
- Code > Download zip을 클릭하여 이 Codelab의 모든 코드를 다운로드합니다.
- 다운로드한 ZIP 파일의 압축을 풀어 필요한 모든 리소스가 포함된
codelabs-main
루트 폴더의 압축을 풉니다.
이 Codelab에서는 저장소의 tfagents-flutter/
하위 디렉터리에 있는 파일만 필요하며, 이 파일에는 여러 폴더가 포함됩니다.
step0
~step6
폴더에는 이 Codelab의 각 단계에서 빌드의 기반이 되는 시작 코드가 포함되어 있습니다.finished
폴더에는 완료된 샘플 앱의 완성된 코드가 포함되어 있습니다.- 각 폴더에는 백엔드 코드가 포함된
backbend
하위 폴더와 Flutter 프런트엔드 코드가 포함된frontend
하위 폴더가 포함됩니다.
5. 프로젝트의 종속 항목 다운로드
백엔드
터미널을 열고 tfagents-flutter
하위 폴더로 이동합니다. 다음을 실행합니다.
pip install -r requirements.txt
프런트엔드
- VS Code에서 File > 폴더를 열고 이전에 다운로드한 소스 코드에서
step0
폴더를 선택합니다. step0/frontend/lib/main.dart
파일을 엽니다. 시작 앱에 필요한 패키지를 다운로드하라는 VS Code 대화상자가 표시되면 Get packages를 클릭합니다.- 이 대화상자가 표시되지 않으면 터미널을 열고
step0/frontend
폴더에서flutter pub get
명령어를 실행합니다.
6. 0단계: 시작 앱 실행
- VS Code에서
step0/frontend/lib/main.dart
파일을 열고 Android Emulator 또는 iOS 시뮬레이터가 올바르게 설정되어 있고 상태 표시줄에 표시되는지 확인합니다.
예를 들어 Android Emulator로 Pixel 5를 사용하는 경우 다음과 같이 표시됩니다.
iOS 시뮬레이터로 iPhone 13을 사용하는 경우 다음과 같이 표시됩니다.
Start debugging을 클릭합니다.
앱 실행 및 탐색
Android Emulator 또는 iOS 시뮬레이터에서 앱이 실행됩니다. UI는 매우 단순합니다. 게임 보드는 2개입니다. 플레이어는 요원 보드 상단에 있는 아무 셀이나 스트라이크 위치로 탭할 수 있습니다. 인간 플레이어의 보드에 따라 적을 공격할 위치를 자동으로 예측하도록 스마트 에이전트를 학습시킬 것입니다.
내부적으로 Flutter 앱은 플레이어의 현재 보드를 백엔드로 전송하며, 백엔드는 강화 학습 모델을 실행하고 다음에 공격할 예측 셀 위치를 반환합니다. 응답을 받은 후 프런트엔드에서 UI에 결과를 표시합니다.
지금 에이전트 보드에서 셀을 클릭해도 앱이 아직 백엔드와 통신할 수 없으므로 아무 일도 일어나지 않습니다.
7. 1단계: TensorFlow 에이전트 Python 환경 만들기
이 Codelab의 주요 목표는 환경과 상호작용하여 학습하는 에이전트를 설계하는 것입니다. 비행기 스트라이크 게임은 비교적 단순하고 NPC 요원을 위한 규칙을 직접 만들 수 있지만, 강화 학습을 통해 요원을 교육하면 기술을 익히고 향후 다른 게임의 요원을 쉽게 만들 수 있습니다.
표준 강화 학습 (RL) 설정에서 상담사는 매 단계에서 관찰을 받고 액션을 선택합니다. 작업이 환경에 적용되고 환경이 보상과 새로운 관찰을 반환합니다. 에이전트는 리워드 합계를 극대화하는 작업(반품이라고도 함)을 선택하도록 정책을 학습시킵니다. 게임을 여러 번 플레이함으로써 에이전트는 패턴을 배우고 게임을 마스터하기 위한 기술을 연마할 수 있습니다. 플레인 스트라이크 게임을 RL 문제로 설명하려면 보드 상태를 관찰이라고 하고, 스트라이크 위치를 동작으로, 적중/실패 신호를 보상으로 생각하세요.
NPC 에이전트를 학습시키기 위해서는 TensorFlow용 안정적이고 확장 가능하며 사용하기 쉬운 강화 학습 라이브러리인 TensorFlow 에이전트를 활용합니다.
TF 에이전트는 시작하는 데 필요한 광범위한 Codelab, 예시, 광범위한 문서가 제공되므로 강화 학습에 적합합니다. TF 에이전트를 사용하면 확장성을 갖춘 현실적이고 복잡한 RL 문제를 해결하고 새로운 RL 알고리즘을 신속하게 개발할 수 있습니다. 다양한 에이전트와 실험 알고리즘 간에 쉽게 전환할 수 있습니다. 또한 테스트가 잘 되었으며 구성하기도 쉽습니다.
OpenAI Gym에는 사전 빌드된 게임 환경이 많이 있습니다 (예: Atari 게임), Mujuco 등 여러 가지 유형의 게임과 같은 기존 기술을 사용할 수 있습니다. 하지만 비행기 스트라이크 게임은 완전한 맞춤형 게임이므로, 먼저 새로운 환경을 처음부터 구현해야 합니다.
TF 에이전트 Python 환경을 구현하려면 다음 메서드를 구현해야 합니다.
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."""
가장 중요한 함수는 _step()
함수로, 작업을 실행하고 새 time_step
객체를 반환합니다. Plane Strike 게임의 경우 게임 보드가 있습니다. 게임 보드 상태에 따라 새로운 공격 위치가 들어오면 환경에서 다음을 파악합니다.
- 이제 게임 보드는 어떻게 생겼을까요? (숨겨진 평면 위치를 고려하여 셀의 색을 빨간색 또는 노란색으로 변경해야 할까요?)
- 해당 위치에 대해 플레이어가 받아야 하는 보상 (리워드 달성 또는 페널티 누락)
- 경기 종료 여부 (누구나 이겼나요?)
- 다음 코드를
_step()
함수의_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. 2단계: TensorFlow 에이전트를 사용한 게임 에이전트 학습
TF 에이전트 환경을 갖추면 게임 에이전트를 학습시킬 수 있습니다. 이 Codelab에서는 REINFORCE 에이전트를 사용합니다. REINFORCE는 RL의 정책 경사 알고리즘입니다. 기본 아이디어는 정책 네트워크가 향후 플레이에서 수익을 극대화할 수 있도록 게임플레이 중에 수집된 리워드 신호를 기반으로 정책 신경망 매개변수를 조정하는 것입니다.
- 먼저 학습 및 평가 환경을 인스턴스화해야 합니다. 다음 코드를
step2/backend/training.py
파일의train_agent()
함수에 추가합니다.
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)
- 다음으로 학습시킬 강화 학습 에이전트를 만들어야 합니다. 이 Codelab에서는 정책 기반 에이전트인 REINFORCE 에이전트를 사용합니다. 위 코드 바로 아래에 이 코드를 추가합니다.
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, )
- 마지막으로 학습 루프에서 에이전트를 학습시킵니다. 루프에서는 먼저 게임 플레이의 몇 가지 에피소드를 버퍼로 수집한 다음 버퍼링된 데이터로 에이전트를 학습시킵니다. 다음 코드를
step2/backend/training.py
파일의train_agent()
함수에 추가합니다.
# 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()
- 이제 교육을 시작할 수 있습니다. 터미널에서 컴퓨터의
step2/backend
폴더로 이동하여 다음을 실행합니다.
python training.py
하드웨어 구성에 따라 학습을 완료하는 데 8~12시간이 걸립니다 (사전 학습된 모델이 step3
에 제공되므로 전체 학습을 직접 완료하지 않아도 됨). 그동안 TensorBoard를 사용하여 진행 상황을 모니터링할 수 있습니다. 새 터미널을 열고 컴퓨터의 step2/backend
폴더로 이동하여 다음을 실행합니다.
tensorboard --logdir tf_agents_log/
tf_agents_log
은 학습 로그가 포함된 폴더입니다. 샘플 학습 실행은 다음과 같습니다.
학습이 진행됨에 따라 평균 에피소드 길이가 줄어들고 평균 수익이 증가하는 것을 확인할 수 있습니다. 에이전트가 더 스마트하고 더 나은 예측을 할 경우 게임 길이가 짧아지고 에이전트가 더 많은 보상을 수집한다는 것을 직관적으로 이해할 수 있습니다. 이는 에이전트가 이후 단계에서 큰 보상 할인을 최소화하기 위해 더 적은 단계로 게임을 완료하고자 하기 때문입니다.
학습이 완료되면 학습된 모델을 policy_model
폴더로 내보냅니다.
9. 3단계: TensorFlow Serving으로 학습된 모델 배포
이제 게임 에이전트를 학습했으므로 TensorFlow Serving으로 배포할 수 있습니다.
- 터미널에서 컴퓨터의
step3/backend
폴더로 이동하여 Docker를 사용하는 TensorFlow Serving을 시작합니다.
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는 먼저 TensorFlow Serving 이미지를 자동으로 다운로드하며, 여기에 몇 분 정도 걸립니다. 그런 다음 TensorFlow Serving이 시작됩니다. 로그는 다음 코드 스니펫과 같아야 합니다.
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 ...
엔드포인트에 샘플 요청을 전송하여 예상대로 작동하는지 확인할 수 있습니다.
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
엔드포인트는 보드 중앙의 (5, 5)인 예상 위치 45
을 반환합니다. 궁금한 점이 있으면 보드 중앙이 1차 스트라이크 포지션에 적합한 추측인 이유를 확인해 볼 수 있습니다.
{ "predictions": [45] }
작업이 끝났습니다. NPC 요원의 다음 공격 위치를 예측하는 백엔드를 성공적으로 빌드했습니다.
10. 4단계: Android 및 iOS용 Flutter 앱 만들기
백엔드가 준비되었습니다. Flutter 앱에서 스트라이크 위치 예측을 가져오기 위해 요청을 보낼 수 있습니다.
- 먼저 전송할 입력을 래핑하는 클래스를 정의해야 합니다. 다음 코드를
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; } }
이제 TensorFlow Serving에 요청을 보내 예측을 수행할 수 있습니다.
- 다음 코드를
step4/frontend/lib/game_agent.dart
파일의predict()
함수에 추가합니다.
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'); }
앱이 백엔드로부터 응답을 수신하면 게임 진행 상황을 반영하도록 게임 UI를 업데이트합니다.
- 다음 코드를
step4/frontend/lib/main.dart
파일의_gridItemTapped()
함수에 추가합니다.
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(() {});
실행하기
Start debugging을 클릭하고 앱이 로드될 때까지 기다립니다.
- 에이전트 보드에서 아무 셀이나 탭하여 게임을 시작합니다.
11. 5단계: 데스크톱 플랫폼용 Flutter 앱 사용 설정
Flutter는 Android와 iOS 외에도 Linux, Mac, Windows를 비롯한 데스크톱 플랫폼을 지원합니다.
Linux
- VSCode의 상태 표시줄에서 대상 기기가
로 설정되어 있는지 확인합니다.
Start debugging을 클릭하고 앱이 로드될 때까지 기다립니다.
- 에이전트 보드에서 아무 셀이나 클릭하여 게임을 시작하세요.
Mac
- Mac의 경우 앱이 HTTP 요청을 백엔드로 전송하므로 적절한 사용 권한을 설정해야 합니다. 자세한 내용은 사용 권한 및 앱 샌드박스를 참고하세요.
다음 코드를 각각 step4/frontend/macOS/Runner/DebugProfile.entitlements
및 step4/frontend/macOS/Runner/Release.entitlements
에 추가합니다.
<key>com.apple.security.network.client</key>
<true/>
- VSCode의 상태 표시줄에서 대상 기기가
로 설정되어 있는지 확인합니다.
Start debugging을 클릭하고 앱이 로드될 때까지 기다립니다.
- 에이전트 보드에서 아무 셀이나 클릭하여 게임을 시작하세요.
Windows
- VSCode의 상태 표시줄에서 대상 기기가
로 설정되어 있는지 확인합니다.
Start debugging을 클릭하고 앱이 로드될 때까지 기다립니다.
- 에이전트 보드에서 아무 셀이나 클릭하여 게임을 시작하세요.
12. 6단계: 웹 플랫폼용 Flutter 앱 사용 설정
한 가지 더 할 수 있는 일은 Flutter 앱에 웹 지원을 추가하는 것입니다. 기본적으로 웹 플랫폼은 Flutter 앱에 자동으로 사용 설정되어 있으므로 실행하기만 하면 됩니다.
- VSCode의 상태 표시줄에서 대상 기기가
로 설정되어 있는지 확인합니다.
Start debugging을 클릭하고 앱이 Chrome 브라우저에 로드될 때까지 기다립니다.
- 에이전트 보드에서 아무 셀이나 클릭하여 게임을 시작하세요.
13. 축하합니다
ML 기반 에이전트로 실제 사람과 대결하는 보드게임 앱을 빌드했습니다.