    def run(self):
        game = self.game
        game_view = self.game_view
        try:
            clock = pygame.time.Clock()
            result = None
            self._send_system_message("AI準備中")

            self._send_game_info(game.get_scene_init_data())

            self._wait_all_ml_ready()
            self._send_system_message("遊戲啟動")
            scene_info_dict = game.get_data_from_game_to_player()
            keyboard_info = game_view.get_keyboard_info()
            self._send_data_to_ai_clients(scene_info_dict, keyboard_info)
            while not self._quit_or_esc():
                # handle IO
                clock.tick(GAME_LOOP_RATE)
                keyboard_info = game_view.get_keyboard_info()
                #
                current_time = pygame.time.get_ticks()
                if game_view.is_paused():
                    # 這裡的寫法不太好，但是可以讓遊戲暫停時，可以調整畫面。因為 game_view 裡面有調整畫面的程式。
                    game_view.draw(self._view_data)
                    self.sound_controller.pause()
                    continue


                # cmd_dict = self._make_ml_execute(
                #     scene_info_dict, keyboard_info)

                # do game logic per 1/30 second
                # self._recorder.record(scene_info_dict, cmd_dict)
                # logger.debug(f"update game state at {self._frame_count}")

                if current_time - self._last_game_updated_at > self._game_updating_span:
                    # send command from ai client
                    cmd_dict = self._receive_cmd_from_ai_clients()
                    result = game.update(cmd_dict)
                    scene_info_dict = game.get_data_from_game_to_player()

                    # get command from ai client
                    # 這裡拿到的是什麼時候的資料呢？
                    self._frame_count += 1
                    self._total_frame += 1
                    self._view_data = game.get_scene_progress_data()
                    logger.debug(f"get_scene_progress_data at {self._frame_count}")
                    self._send_game_progress(self._view_data)
                    self._send_data_to_ai_clients(scene_info_dict, keyboard_info)
                    self._last_game_updated_at = current_time


                if current_time - self._last_screen_updated_at > self._screen_updating_span:
                    logger.debug(f"game_view.draw at {self._frame_count}")
                    game_view.draw(self._view_data)
                    logger.debug(f"_play_audio at {self._frame_count}")
                    self._play_audio(self._view_data)
                    self._last_screen_updated_at = current_time


                # save image
                if self._output_folder:
                    game_view.save_image(f"{self._output_folder}/{self._frame_count:05d}.jpg")
                

                # Do reset stuff
                logger.debug(f"reset or quit by {result} at {self._frame_count}")

                if result == "RESET" or result == "QUIT":
                    scene_info_dict = game.get_data_from_game_to_player()
                    # send to ml_clients and don't parse any command , while client reset ,
                    # self._wait_all_ml_ready() will works and not blocks the process
                    for ml_name in self._active_ml_names:
                        self.game_comm.send_to_ml((scene_info_dict[ml_name], []), ml_name)
                    # TODO check what happen when bigfile is saved
                    time.sleep(0.1)
                    game_result = game.get_game_result()

                    attachments = game_result['attachment']
                    print(pd.DataFrame(attachments).to_string())

                    if self.one_shot_mode or result == "QUIT":
                        self._send_system_message("遊戲結束")
                        self._send_game_result(game_result)
                        if self._output_folder:
                            save_json(self._output_folder, game_result)
                        self._send_system_message("關閉遊戲")
                        self._send_end_message()
                        time.sleep(1)

                        break
                    logger.warning(f"game reset at {self._frame_count}")

                    game.reset()
                    game_view.reset()

                    self._frame_count = 0
                    # TODO think more
                    for name in self._active_ml_names:
                        self._ml_delayed_frames[name] = 0
                    logger.debug(f"_wait_all_ml_ready at {self._frame_count}")

                    self._wait_all_ml_ready()
        except Exception:
            # handle unknown exception
            # send to es
            e = GameProcessError(self._proc_name, traceback.format_exc())
            logger.exception(e)
            self._send_game_error_with_obj(GameError(
                error_type=ErrorEnum.GAME_EXEC_ERROR,
                message=e.__str__(),
                frame=self._frame_count,

            ))

        pass