def a_vs_b(ship_a, ship_b, trials, attack_range):
    """This function calculates the average time to destruction when a shoots at b.

    Args:
      ship_a ((Ship, str)): Attacker and hull zone tuple.
      ship_b ((Ship, str)): Defender and hull zone tuple.
      trials (int): Number of trials in average calculation.
      range (str): Attack range.
    
    """
    roll_counts = []
    agent = SimpleAgent()
    for trial in range(trials):
        # Reset ship b for each trial
        ship_b.reset()
        world_state = WorldState()
        world_state.addShip(ship_a, 0)
        world_state.addShip(ship_b, 1)
        num_rolls = 0
        while ship_b.damage_cards() < ship_b.hull():
            num_rolls += 1
            # Handle the attack and receive the updated world state
            world_state = handleAttack(world_state=world_state,
                                       attacker=(ship_a, "front"),
                                       defender=(ship_b, "front"),
                                       attack_range=attack_range,
                                       offensive_agent=agent,
                                       defensive_agent=agent)
        roll_counts.append(num_rolls)
    np_counts = numpy.array(roll_counts)
    return np_counts.mean()
def a_vs_b(ship_a, ship_b, agent_a, agent_b, ship_a_hull, trials,
           attack_range):
    """This function runs multiple trials of ship_a firing upon ship_b.

    Args:
        ship_a ((Ship, str)): Attacker and hull zone tuple.
        ship_b ((Ship, str)): Defender and hull zone tuple.
        agent_a  (BaseAgent): Agent to control the actions of ship a.
        agent_b  (BaseAgent): Agent to control the actions of ship b.
        ship_a_hull (str)   : Attacking hull zone.
        trials (int): Number of trials in average calculation.
        range (str): Attack range.
    Returns:
        List[(str, world_state or attack effect tuple)]
    """
    state_log = []
    failures = 0
    for _ in range(trials):
        # Reset ship b for each trial
        ship_b.reset()
        world_state = WorldState()
        world_state.addShip(ship_a, 0)
        world_state.addShip(ship_b, 1)
        # Begin at round 1
        world_state.round = 1
        # Don't attempt forever in the case of some catastrophic reoccurring error.
        attempts = 0
        while ship_b.damage_cards() < ship_b.hull(
        ) and world_state.round <= ArmadaPhases.max_rounds:
            attempts += 1
            # Handle the attack and receive the updated world state
            #try:
            world_state = handleAttack(world_state=world_state,
                                       attacker=(ship_a, ship_a_hull),
                                       defender=(ship_b, "front"),
                                       attack_range=attack_range,
                                       offensive_agent=agent_a,
                                       defensive_agent=agent_b,
                                       state_log=state_log)
            # Record the final state with the incremented round number.
            world_state.setPhase("status phase", "increment round number")
            world_state.round += 1
            state_log.append(('state', world_state.clone()))
            #except RuntimeError as err:
            #    # This is fine, the random agent will do illegal things plenty of times
            #    pass
        if 250 == attempts:
            raise RuntimeError("Too many failures for ship firing simulation.")
    return state_log
def a_vs_b(ship_a, ship_b, trials, attack_range):
    """This uses a random agent to choose actions during attacks from ship_a to ship_b.

    Args:
      ship_a ((Ship, str)): Attacker and hull zone tuple.
      ship_b ((Ship, str)): Defender and hull zone tuple.
      trials (int): Number of trials in average calculation.
      range (str): Attack range.
    Returns:
      state_log (List[List[("state" or "action", (WorldState or action tuple))]])
    
    """
    agent = RandomAgent()
    state_log = []
    for trial in range(trials):
        # Reset ship b for each trial
        ship_b.reset()
        world_state = WorldState()
        world_state.addShip(ship_a, 0)
        world_state.addShip(ship_b, 1)
        num_rolls = 0
        while ship_b.damage_cards() < ship_b.hull():
            num_rolls += 1
            # Handle the attack and receive the updated world state
            try:
                world_state = handleAttack(world_state=world_state,
                                           attacker=(ship_a, "front"),
                                           defender=(ship_b, "front"),
                                           attack_range=attack_range,
                                           offensive_agent=agent,
                                           defensive_agent=agent,
                                           state_log=state_log)
            except RuntimeError:
                # This is fine, the random agent will do illegal things plenty of times
                pass
    return state_log
 world_state = WorldState()
 world_state.addShip(attacker, 0)
 world_state.addShip(defender, 1)
 num_rolls = 0
 # This will hold lists of the state action pairs for each roll
 state_actions = []
 while 0 < defender.hull():
     num_rolls += 1
     # Handle the attack and receive the updated world state
     # In this initial version of the code the prediction agent won't actually take any
     # actions but we need it to log the (attack_state, action) pairs
     prediction_agent.rememberStateActions()
     # TOOD FIXME HERE For random training sometimes the agents should be random agents.
     world_state = handleAttack(world_state=world_state,
                                attacker=(attacker, attack_hull),
                                defender=(defender, defend_hull),
                                attack_range=attack_range,
                                offensive_agent=prediction_agent,
                                defensive_agent=prediction_agent)
     world_state.round += 1
     # Get the (state, action) pairs back
     state_action_pairs = prediction_agent.returnStateActions()
     state_actions.append(state_action_pairs)
     if args.novelty:
         logging.info("\t roll {}, estimate {}, novelty {}".format(
             num_rolls, state_action_pairs[0][2][-2],
             state_action_pairs[0][3]))
     else:
         logging.info("\t roll {}, estimate {}".format(
             num_rolls, state_action_pairs[0][2][-2]))
 # Pair the lifetimes with the (state, action) pairs and push them into the examples list
 for roll_idx, state_action_pairs in enumerate(state_actions):
             logging.info("Trial number {}".format(trial))
             # Reset ship b for each trial
             ship_2 = ship.Ship(name=ship_name_2,
                                template=ship_templates[ship_name_2],
                                upgrades=[],
                                player_number=2)
             world_state = WorldState()
             world_state.addShip(ship_1, 0)
             world_state.addShip(ship_2, 1)
             num_rolls = 0
             while 0 < ship_2.hull():
                 num_rolls += 1
                 # Handle the attack and receive the updated world state
                 world_state = handleAttack(world_state=world_state,
                                            attacker=(ship_1, "front"),
                                            defender=(ship_2, "front"),
                                            attack_range=attack_range,
                                            offensive_agent=agent,
                                            defensive_agent=agent)
             roll_counts.append(num_rolls)
         np_counts = numpy.array(roll_counts)
         print(
             "Ship {} destroys {} in {} average rolls, stddev = {}, at range {}."
             .format(ship_name_1, ship_name_2, np_counts.mean(),
                     np_counts.var()**0.5, attack_range))
         logging.info(
             "Ship {} destroys {} in {} average rolls, stddev = {}, at range {}.\n"
             .format(ship_name_1, ship_name_2, np_counts.mean(),
                     np_counts.var()**0.5, attack_range))
 # TODO(take defense tokens into account)
 #  Make a base player class that allows you to bind a function for this stuff
 # TODO(take accuracy into account)