GameState GameState::process_m()

in dipcc/dipcc/cc/adjudicator.cc [1260:1476]


GameState GameState::process_m(
    const std::unordered_map<Power, std::vector<Order>> &orders,
    bool exception_on_convoy_paradox) {
  JCHECK(this->get_phase().phase_type == 'M',
         "Bad phase_type: " + this->get_phase().phase_type);
  DLOG(INFO) << "Process phase: " << this->get_phase().to_string();

  const unordered_map<Loc, set<Order>> &all_possible_orders(
      this->get_all_possible_orders());

  // Build up candidate data
  LocCandidates loc_candidates;
  vector<pair<Order, bool>> move_via_orders;
  vector<Order> support_orders;
  set<Loc> illegal_orderers;
  set<Loc> unconvoyed_movers;

  // Set debugging flags
  loc_candidates.exception_on_convoy_paradox_ = exception_on_convoy_paradox;

  // First add all existing units as candidates to remain in their
  // current location
  for (auto &it : this->units_) {
    Loc loc = it.first;
    OwnedUnit unit = it.second;
    loc_candidates.add_candidate(loc, unit, false, false);
  }

  // Organize orders by src loc
  unordered_map<Loc, Order> orders_by_src;
  for (auto & [ power, porders ] : orders) {
    for (const Order order : porders) {
      Loc loc = order.get_unit().loc;
      // check if correct power ordering unit
      if (this->get_unit(loc).power != power) {
        DLOG(WARNING) << power_str(power)
                      << " tried wrong-power order: " << order.to_string();
        continue;
      }
      orders_by_src[root_loc(loc)] = order;
    }
  }

  // Loop through all orders and build up data structures
  for (auto & [ rloc, order ] : orders_by_src) {
    // check if order is possible
    auto loc_possible_orders_it =
        all_possible_orders.find(order.get_unit().loc);
    if (loc_possible_orders_it == all_possible_orders.end() ||
        !set_contains(loc_possible_orders_it->second, order)) {
      if (is_implicit_via(order, all_possible_orders)) {
        // set via to explicitly true and move on
        DLOG(WARNING) << "Accepting implicit via for order: "
                      << order.to_string();
        order = order.with_via(true);
        orders_by_src[root_loc(order.get_unit().loc)] = order;
      } else {
        DLOG(WARNING) << "Order not possible: " << order.to_string();
        illegal_orderers.insert(root_loc(order.get_unit().loc));
        continue;
      }
    }

    // check if via move is to adjacent loc (i.e. non-via move also
    // allowed)
    bool via_adj =
        (order.get_via() &&
         loc_possible_orders_it != all_possible_orders.end() &&
         set_contains(loc_possible_orders_it->second, order.with_via(false)));

    // add all loc candidates and set aside supports
    if (order.get_type() == OrderType::H) {
      // do nothing, hold candidates already added
    } else if (order.get_type() == OrderType::M) {
      if (order.get_via()) {
        // Handle via moves after gathering convoy orders.
        move_via_orders.push_back(make_pair(order, via_adj));
      } else {
        // move to dest with max=1
        loc_candidates.add_candidate(order.get_dest(),
                                     this->get_unit(order.get_unit().loc),
                                     false, false);
      }
    } else if (order.get_type() == OrderType::SM ||
               order.get_type() == OrderType::SH) {
      // handle supports after determining which moves are legal
      support_orders.push_back(order);
    } else if (order.get_type() == OrderType::C) {
      auto target = orders_by_src.find(root_loc(order.get_target().loc));
      if (target != orders_by_src.end() &&
          target->second.get_type() == OrderType::M &&
          target->second.get_dest() == order.get_dest() &&
          (target->second.get_via() ||
           is_implicit_via(target->second, all_possible_orders))) {
        loc_candidates.add_convoy_order(order);
      } else {
        DLOG(WARNING) << "Uncoordinated convoy: " << order.to_string();
      }
    } else {
      throw("Can't yet categorize order: " + order.to_string());
    }
  }

  // Check for valid convoy path before adding move via order. Move may still
  // fail if a convoying fleet is dislodged
  for (auto & [ order, via_adj ] : move_via_orders) {
    if (loc_candidates.is_convoy_possible(root_loc(order.get_unit().loc),
                                          root_loc(order.get_dest()))) {
      loc_candidates.add_candidate(order.get_dest(),
                                   this->get_unit(order.get_unit().loc),
                                   order.get_via(), via_adj);
    } else {
      DLOG(INFO) << "Unconvoyed via move: " << order.to_string();
      loc_candidates.erase_all_pending_convoys(order.get_unit().loc,
                                               order.get_dest());

      if (via_adj) {
        DLOG(INFO) << "Unconvoyed via move converted to normal move: "
                   << order.to_string();
        loc_candidates.add_candidate(order.get_dest(),
                                     this->get_unit(order.get_unit().loc),
                                     false, false);
      } else {
        unconvoyed_movers.insert(order.get_unit().loc);
      }
    }
  }

  // Resolve supports
  for (Order &order : support_orders) {
    // Check for support coordination, e.g. that we are not support-holding a
    // unit that is moving, or support-moving a unit to the wrong destination
    auto target = orders_by_src.find(root_loc(order.get_target().loc));

    if (set_contains(unconvoyed_movers, root_loc(order.get_target().loc))) {
      DLOG(WARNING) << "Support of unconvoyed mover: " << order.to_string();
      continue;
    } else if (order.get_type() == OrderType::SM &&
               (target == orders_by_src.end() ||
                target->second.get_type() != OrderType::M ||
                // allow SM to specify exact dest or root dest
                (order.get_dest() != target->second.get_dest() &&
                 order.get_dest() != root_loc(target->second.get_dest())))) {
      DLOG(WARNING) << "Uncoordinated support-move: " << order.to_string();
      continue;
    } else if (order.get_type() == OrderType::SH &&
               (target != orders_by_src.end() &&
                target->second.get_type() == OrderType::M &&
                !set_contains(illegal_orderers,
                              root_loc(target->second.get_unit().loc)))) {
      DLOG(WARNING) << "Uncoordinated support-hold: " << order.to_string();
      continue;
    }

    // Check for support cuts.  Anyone (of a different power) trying to move
    // to any coastal variant is a cut candidate
    Power supporter_power = this->get_unit(order.get_unit().loc).power;
    set<Loc> cut_candidates;
    set<Loc> convoy_cut_candidates;
    for (Loc loc : expand_coasts(order.get_unit().loc)) {
      for (LocCandidate move_cand : loc_candidates.get_move_candidates(loc)) {
        if (move_cand.power == supporter_power) {
          // can't cut own support
          continue;
        }
        if (move_cand.via) {
          DLOG(INFO) << "Convoy cut candidate " << order.to_string() << " : "
                     << root_loc(move_cand.src);
          convoy_cut_candidates.insert(root_loc(move_cand.src));
        } else {
          DLOG(INFO) << "Cut candidate " << order.to_string() << " : "
                     << root_loc(move_cand.src);
          cut_candidates.insert(root_loc(move_cand.src));
        }
      }
    }

    if (order.get_type() == OrderType::SH) {
      //
      // handle support-hold
      //
      if (cut_candidates.size() == 0 && convoy_cut_candidates.size() == 0) {
        // support-hold is not cut, increase strength
        loc_candidates.add_support(order, supporter_power);
      } else if (cut_candidates.size() == 0 &&
                 convoy_cut_candidates.size() > 0) {
        // support cut depends on convoy
        loc_candidates.add_unresolved_support(order, supporter_power, false);
      } else {
        // cut_candidates > 0, support is cut
      }
    } else {
      //
      // handle support-move
      //
      if (cut_candidates.size() == 0 && convoy_cut_candidates.size() == 0) {
        // support-move is not cut, increase strength
        loc_candidates.add_support(order, supporter_power);
      } else if (cut_candidates.size() == 0 &&
                 convoy_cut_candidates.size() > 0) {
        // support cut depends on convoy
        loc_candidates.add_unresolved_support(order, supporter_power, false);
      } else if (cut_candidates.size() == 1) {
        // pending_dislodge: potential cutter is being attacked by this
        // support: support is conditional on dislodgedment (see DATC 6.D.17)
        if (root_loc(*cut_candidates.begin()) == root_loc(order.get_dest())) {
          loc_candidates.add_unresolved_support(order, supporter_power, true);
        }
      }
    }
  }

  // Resolve moves
  loc_candidates.log();
  auto resolved = loc_candidates.resolve();
  return build_next_state(resolved);
}