code: def select_actions(root, action_space, max_episode_steps):
      
      node = root
  
      acts = []
      steps = 0
      while steps < max_episode_steps:
          if node is None:
              # we've fallen off the explored area of the tree, just select random actions
              act = action_space.sample()
          else:
              epsilon = EXPLORATION_PARAM / np.log(node.visits + 2)
              if random.random() < epsilon:
                  # random action
                  act = action_space.sample()
              else:
                  # greedy action
                  act_value = {}
                  for act in range(action_space.n):
                      if node is not None and act in node.children:
                          act_value[act] = node.children[act].value
                      else:
                          act_value[act] = -np.inf
                  best_value = max(act_value.values())
                  best_acts = [
                      act for act, value in act_value.items() if value == best_value
                  ]
                  act = random.choice(best_acts)
  
              if act in node.children:
                  node = node.children[act]
              else:
                  node = None
  
          acts.append(act)
          steps += 1
  
      return acts
docs: Select actions from the tree
  
      Normally we select the greedy action that has the highest reward
      associated with that subtree.  We have a small chance to select a
      random action based on the exploration param and visit count of the
      current node at each step.
  
      We select actions for the longest possible episode, but normally these
      will not all be used.  They will instead be truncated to the length
      of the actual episode and then used to update the tree.