code: def on_train_result(self, *, algorithm, result, **kwargs):
      # Get the win rate for the train batch.
      # Note that normally, one should set up a proper evaluation config,
      # such that evaluation always happens on the already updated policy,
      # instead of on the already used train_batch.
      main_rew = result["hist_stats"].pop("policy_main_reward")
      opponent_rew = list(result["hist_stats"].values())[0]
      assert len(main_rew) == len(opponent_rew)
      won = 0
      for r_main, r_opponent in zip(main_rew, opponent_rew):
          if r_main > r_opponent:
              won += 1
      win_rate = won / len(main_rew)
      result["win_rate"] = win_rate
      print(
          f"Iter={algorithm.iteration} win-rate={win_rate} -> ", end=""
      )
      # If win rate is good -> Snapshot current policy and play against
      # it next, keeping the snapshot fixed and only improving the "main"
      # policy.
      if win_rate > args.win_rate_threshold:
          self.current_opponent += 1
          new_pol_id = f"main_v{self.current_opponent}"
          print(f"adding new opponent to the mix ({new_pol_id}).")
  
          # Re-define the mapping function, such that "main" is forced
          # to play against any of the previously played policies
          # (excluding "random").
          def policy_mapping_fn(agent_id, episode, worker, **kwargs):
              # agent_id = [0|1] -> policy depends on episode ID
              # This way, we make sure that both policies sometimes play
              # (start player) and sometimes agent1 (player to move 2nd).
              return (
                  "main"
                  if episode.episode_id % 2 == agent_id
                  else "main_v{}".format(
                      np.random.choice(
                          list(range(1, self.current_opponent + 1))
                      )
                  )
              )
  
          main_policy = algorithm.get_policy("main")
          if algorithm.config._enable_new_api_stack:
              new_policy = algorithm.add_policy(
                  policy_id=new_pol_id,
                  policy_cls=type(main_policy),
                  policy_mapping_fn=policy_mapping_fn,
                  module_spec=SingleAgentRLModuleSpec.from_module(
                      main_policy.model
                  ),
              )
          else:
              new_policy = algorithm.add_policy(
                  policy_id=new_pol_id,
                  policy_cls=type(main_policy),
                  policy_mapping_fn=policy_mapping_fn,
              )
  
          # Set the weights of the new policy to the main policy.
          # We'll keep training the main policy, whereas `new_pol_id` will
          # remain fixed.
          main_state = main_policy.get_state()
          new_policy.set_state(main_state)
          # We need to sync the just copied local weights (from main policy)
          # to all the remote workers as well.
          algorithm.workers.sync_weights()
      else:
          print("not good enough; will keep learning ...")
  
      # +2 = main + random
      result["league_size"] = self.current_opponent + 2
  
imports: import argparse
  import os
  import sys
  import numpy
  import ray
  from ray import air, tune
  from ray.rllib.algorithms.callbacks import DefaultCallbacks
  from ray.rllib.env.utils import try_import_pyspiel, try_import_open_spiel
  from ray.rllib.env.wrappers.open_spiel import OpenSpielEnv
  from ray.rllib.examples.policy.random_policy import RandomPolicy
  from ray.rllib.policy.policy import PolicySpec
  from ray.rllib.utils.test_utils import check_learning_achieved
  from ray.tune import CLIReporter, register_env
  from ray.tune.registry import get_trainable_cls
  from ray.rllib.core.rl_module.rl_module import SingleAgentRLModuleSpec
  from ray.rllib.core.rl_module.marl_module import MultiAgentRLModuleSpec
  from ray.rllib.examples.rl_module.random_rl_module import RandomRLModule
  from open_spiel.python.rl_environment import Environment
label_desc: Use image node_id 2 for calling the SelfPlayCallback obj's underlying member method code with example usage: obj.on_train_result() and returns: unknown