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);
}