def get_adjacency_matrix(map_name='standard'): """ Computes the adjacency matrix for map :param map_name: The name of the map :return: A (nb_nodes) x (nb_nodes) matrix """ if map_name in ADJACENCY_MATRIX: return ADJACENCY_MATRIX[map_name] # Finding list of all locations current_map = Map(map_name) locs = get_sorted_locs(current_map) adjacencies = np.zeros((len(locs), len(locs)), dtype=np.bool) # Building adjacencies between locs # Coasts are adjacent to their parent location (without coasts) for i, loc_1 in enumerate(locs): for j, loc_2 in enumerate(locs): if current_map.abuts('A', loc_1, '-', loc_2) or current_map.abuts( 'F', loc_1, '-', loc_2): adjacencies[i, j] = 1 if loc_1 != loc_2 and (loc_1[:3] == loc_2 or loc_1 == loc_2[:3]): adjacencies[i, j] = 1 # Storing in cache and returning ADJACENCY_MATRIX[map_name] = adjacencies return adjacencies
def _build_from_string(self, order, game=None): """ Builds this object from a string :type order: str :type game: diplomacy.Game """ # pylint: disable=too-many-return-statements,too-many-branches,too-many-statements # Converting move to retreat during retreat phase if self.phase_type == 'R': order = order.replace(' - ', ' R ') # Splitting into parts words = order.split() # --- Wait / Waive --- # [{"id": "56", "unitID": null, "type": "Wait", "toTerrID": "", "fromTerrID": "", "viaConvoy": ""}] if len(words) == 1 and words[0] == 'WAIVE': self.order_str = 'WAIVE' self.order_dict = {'terrID': None, 'unitType': '', 'type': 'Wait', 'toTerrID': '', 'fromTerrID': '', 'viaConvoy': ''} return # Validating if len(words) < 3: LOGGER.error('Unable to parse the order "%s". Require at least 3 words', order) return short_unit_type, loc_name, order_type = words[:3] if short_unit_type not in 'AF': LOGGER.error('Unable to parse the order "%s". Valid unit types are "A" and "F".', order) return if order_type not in 'H-SCRBD': LOGGER.error('Unable to parse the order "%s". Valid order types are H-SCRBD', order) return if loc_name not in CACHE[self.map_name]['loc_to_ix']: LOGGER.error('Received invalid loc "%s" for map "%s".', loc_name, self.map_name) return # Extracting territories unit_type = {'A': 'Army', 'F': 'Fleet'}[short_unit_type] terr_id = CACHE[self.map_name]['loc_to_ix'][loc_name] # --- Hold --- # {"id": "76", "unitID": "19", "type": "Hold", "toTerrID": "", "fromTerrID": "", "viaConvoy": ""} if order_type == 'H': self.order_str = '%s %s H' % (short_unit_type, loc_name) self.order_dict = {'terrID': terr_id, 'unitType': unit_type, 'type': 'Hold', 'toTerrID': '', 'fromTerrID': '', 'viaConvoy': ''} # --- Move --- # {"id": "73", "unitID": "16", "type": "Move", "toTerrID": "25", "fromTerrID": "", "viaConvoy": "Yes", # "convoyPath": ["22", "69"]}, # {"id": "74", "unitID": "17", "type": "Move", "toTerrID": "69", "fromTerrID": "", "viaConvoy": "No"} elif order_type == '-': if len(words) < 4: LOGGER.error('[Move] Unable to parse the move order "%s". Require at least 4 words', order) LOGGER.error(order) return # Building map map_object = Map(self.map_name) convoy_path = [] # Getting destination to_loc_name = words[3] to_terr_id = CACHE[self.map_name]['loc_to_ix'].get(to_loc_name, None) # Deciding if this move is doable by convoy or not if unit_type != 'Army': via_flag = '' else: # Any plausible convoy path (i.e. where fleets are on water, even though they are not convoying) # is valid for the 'convoyPath' argument reachable_by_land = map_object.abuts('A', loc_name, '-', to_loc_name) via_convoy = bool(words[-1] == 'VIA') or not reachable_by_land via_flag = ' VIA' if via_convoy else '' convoy_path = find_convoy_path(loc_name, to_loc_name, map_object, game) if to_loc_name is None: LOGGER.error('[Move] Received invalid to loc "%s" for map "%s".', to_terr_id, self.map_name) LOGGER.error(order) return self.order_str = '%s %s - %s%s' % (short_unit_type, loc_name, to_loc_name, via_flag) self.order_dict = {'terrID': terr_id, 'unitType': unit_type, 'type': 'Move', 'toTerrID': to_terr_id, 'fromTerrID': '', 'viaConvoy': 'Yes' if via_flag else 'No'} if convoy_path: self.order_dict['convoyPath'] = [CACHE[self.map_name]['loc_to_ix'][loc] for loc in convoy_path[:-1]] # --- Support hold --- # {"id": "73", "unitID": "16", "type": "Support hold", "toTerrID": "24", "fromTerrID": "", "viaConvoy": ""} elif order_type == 'S' and '-' not in words: if len(words) < 5: LOGGER.error('[Support H] Unable to parse the support hold order "%s". Require at least 5 words', order) LOGGER.error(order) return # Getting supported unit to_loc_name = words[4][:3] to_terr_id = CACHE[self.map_name]['loc_to_ix'].get(to_loc_name, None) if to_loc_name is None: LOGGER.error('[Support H] Received invalid to loc "%s" for map "%s".', to_terr_id, self.map_name) LOGGER.error(order) return self.order_str = '%s %s S %s' % (short_unit_type, loc_name, to_loc_name) self.order_dict = {'terrID': terr_id, 'unitType': unit_type, 'type': 'Support hold', 'toTerrID': to_terr_id, 'fromTerrID': '', 'viaConvoy': ''} # --- Support move --- # {"id": "73", "unitID": "16", "type": "Support move", "toTerrID": "24", "fromTerrID": "69", "viaConvoy": ""} elif order_type == 'S': if len(words) < 6: LOGGER.error('Unable to parse the support move order "%s". Require at least 6 words', order) return # Building map map_object = Map(self.map_name) convoy_path = [] # Getting supported unit move_index = words.index('-') to_loc_name = words[move_index + 1][:3] # Removing coast from dest from_loc_name = words[move_index - 1] to_terr_id = CACHE[self.map_name]['loc_to_ix'].get(to_loc_name, None) from_terr_id = CACHE[self.map_name]['loc_to_ix'].get(from_loc_name, None) if to_loc_name is None: LOGGER.error('[Support M] Received invalid to loc "%s" for map "%s".', to_terr_id, self.map_name) LOGGER.error(order) return if from_loc_name is None: LOGGER.error('[Support M] Received invalid from loc "%s" for map "%s".', from_terr_id, self.map_name) LOGGER.error(order) return # Deciding if we are support a move by convoy or not # Any plausible convoy path (i.e. where fleets are on water, even though they are not convoying) # is valid for the 'convoyPath' argument, only if it does not include the fleet issuing the support if words[move_index - 2] != 'F' and map_object.area_type(from_loc_name) == 'COAST': convoy_path = find_convoy_path(from_loc_name, to_loc_name, map_object, game, excluding=loc_name) self.order_str = '%s %s S %s - %s' % (short_unit_type, loc_name, from_loc_name, to_loc_name) self.order_dict = {'terrID': terr_id, 'unitType': unit_type, 'type': 'Support move', 'toTerrID': to_terr_id, 'fromTerrID': from_terr_id, 'viaConvoy': ''} if convoy_path: self.order_dict['convoyPath'] = [CACHE[self.map_name]['loc_to_ix'][loc] for loc in convoy_path[:-1]] # --- Convoy --- # {"id": "79", "unitID": "22", "type": "Convoy", "toTerrID": "24", "fromTerrID": "20", "viaConvoy": "", # "convoyPath": ["20", "69"]} elif order_type == 'C': if len(words) < 6: LOGGER.error('[Convoy] Unable to parse the convoy order "%s". Require at least 6 words', order) LOGGER.error(order) return # Building map map_object = Map(self.map_name) # Getting supported unit move_index = words.index('-') to_loc_name = words[move_index + 1] from_loc_name = words[move_index - 1] to_terr_id = CACHE[self.map_name]['loc_to_ix'].get(to_loc_name, None) from_terr_id = CACHE[self.map_name]['loc_to_ix'].get(from_loc_name, None) if to_loc_name is None: LOGGER.error('[Convoy] Received invalid to loc "%s" for map "%s".', to_terr_id, self.map_name) LOGGER.error(order) return if from_loc_name is None: LOGGER.error('[Convoy] Received invalid from loc "%s" for map "%s".', from_terr_id, self.map_name) LOGGER.error(order) return # Finding convoy path # Any plausible convoy path (i.e. where fleets are on water, even though they are not convoying) # is valid for the 'convoyPath' argument, only if it includes the current fleet issuing the convoy order convoy_path = find_convoy_path(from_loc_name, to_loc_name, map_object, game, including=loc_name) self.order_str = '%s %s C A %s - %s' % (short_unit_type, loc_name, from_loc_name, to_loc_name) self.order_dict = {'terrID': terr_id, 'unitType': unit_type, 'type': 'Convoy', 'toTerrID': to_terr_id, 'fromTerrID': from_terr_id, 'viaConvoy': ''} if convoy_path: self.order_dict['convoyPath'] = [CACHE[self.map_name]['loc_to_ix'][loc] for loc in convoy_path[:-1]] # --- Retreat --- # {"id": "152", "unitID": "18", "type": "Retreat", "toTerrID": "75", "fromTerrID": "", "viaConvoy": ""} elif order_type == 'R': if len(words) < 4: LOGGER.error('[Retreat] Unable to parse the move order "%s". Require at least 4 words', order) LOGGER.error(order) return # Getting destination to_loc_name = words[3] to_terr_id = CACHE[self.map_name]['loc_to_ix'].get(to_loc_name, None) if to_loc_name is None: return self.order_str = '%s %s R %s' % (short_unit_type, loc_name, to_loc_name) self.order_dict = {'terrID': terr_id, 'unitType': unit_type, 'type': 'Retreat', 'toTerrID': to_terr_id, 'fromTerrID': '', 'viaConvoy': ''} # --- Disband (R phase) --- # {"id": "152", "unitID": "18", "type": "Disband", "toTerrID": "", "fromTerrID": "", "viaConvoy": ""} elif order_type == 'D' and self.phase_type == 'R': # Note: For R phase, we disband with the coast self.order_str = '%s %s D' % (short_unit_type, loc_name) self.order_dict = {'terrID': terr_id, 'unitType': unit_type, 'type': 'Disband', 'toTerrID': '', 'fromTerrID': '', 'viaConvoy': ''} # --- Build Army --- # [{"id": "56", "unitID": null, "type": "Build Army", "toTerrID": "37", "fromTerrID": "", "viaConvoy": ""}] elif order_type == 'B' and short_unit_type == 'A': self.order_str = 'A %s B' % loc_name self.order_dict = {'terrID': terr_id, 'unitType': 'Army', 'type': 'Build Army', 'toTerrID': terr_id, 'fromTerrID': '', 'viaConvoy': ''} # -- Build Fleet --- # [{"id": "56", "unitID": null, "type": "Build Fleet", "toTerrID": "37", "fromTerrID": "", "viaConvoy": ""}] elif order_type == 'B' and short_unit_type == 'F': self.order_str = 'F %s B' % loc_name self.order_dict = {'terrID': terr_id, 'unitType': 'Fleet', 'type': 'Build Fleet', 'toTerrID': terr_id, 'fromTerrID': '', 'viaConvoy': ''} # Disband (A phase) # {"id": "152", "unitID": null, "type": "Destroy", "toTerrID": "18", "fromTerrID": "", "viaConvoy": ""} elif order_type == 'D': # For A phase, we disband without the coast loc_name = loc_name[:3] terr_id = CACHE[self.map_name]['loc_to_ix'][loc_name] self.order_str = '%s %s D' % (short_unit_type, loc_name) self.order_dict = {'terrID': terr_id, 'unitType': unit_type, 'type': 'Destroy', 'toTerrID': terr_id, 'fromTerrID': '', 'viaConvoy': ''}
def get_order_vocabulary(): """ Computes the list of all valid orders on the standard map :return: A sorted list of all valid orders on the standard map """ # pylint: disable=too-many-nested-blocks,too-many-branches categories = [ 'H', 'D', 'B', '-', 'R', 'SH', 'S-', '-1', 'S1', 'C1', # Move, Support, Convoy (using 1 fleet) '-2', 'S2', 'C2', # Move, Support, Convoy (using 2 fleets) '-3', 'S3', 'C3', # Move, Support, Convoy (using 3 fleets) '-4', 'S4', 'C4' ] # Move, Support, Convoy (using 4 fleets) orders = {category: set() for category in categories} map_object = Map() locs = sorted([loc.upper() for loc in map_object.locs]) # All holds, builds, and disbands orders for loc in locs: for unit_type in ['A', 'F']: if map_object.is_valid_unit('%s %s' % (unit_type, loc)): orders['H'].add('%s %s H' % (unit_type, loc)) orders['D'].add('%s %s D' % (unit_type, loc)) # Allowing builds in all SCs (even though only homes will likely be used) if loc[:3] in map_object.scs: orders['B'].add('%s %s B' % (unit_type, loc)) # Moves, Retreats, Support Holds for unit_loc in locs: for dest in [ loc.upper() for loc in map_object.abut_list(unit_loc, incl_no_coast=True) ]: for unit_type in ['A', 'F']: if not map_object.is_valid_unit('%s %s' % (unit_type, unit_loc)): continue if map_object.abuts(unit_type, unit_loc, '-', dest): orders['-'].add('%s %s - %s' % (unit_type, unit_loc, dest)) orders['R'].add('%s %s R %s' % (unit_type, unit_loc, dest)) # Making sure we can support destination if not (map_object.abuts(unit_type, unit_loc, 'S', dest) or map_object.abuts(unit_type, unit_loc, 'S', dest[:3])): continue # Support Hold for dest_unit_type in ['A', 'F']: for coast in ['', '/NC', '/SC', '/EC', '/WC']: if map_object.is_valid_unit( '%s %s%s' % (dest_unit_type, dest, coast)): orders['SH'].add('%s %s S %s %s%s' % (unit_type, unit_loc, dest_unit_type, dest, coast)) # Convoys, Move Via for nb_fleets in map_object.convoy_paths: # Skipping long-term convoys if nb_fleets > 4: continue for start, fleets, dests in map_object.convoy_paths[nb_fleets]: for end in dests: orders['-%d' % nb_fleets].add('A %s - %s VIA' % (start, end)) orders['-%d' % nb_fleets].add('A %s - %s VIA' % (end, start)) for fleet_loc in fleets: orders['C%d' % nb_fleets].add('F %s C A %s - %s' % (fleet_loc, start, end)) orders['C%d' % nb_fleets].add('F %s C A %s - %s' % (fleet_loc, end, start)) # Support Move (Non-Convoyed) for start_loc in locs: for dest_loc in [ loc.upper() for loc in map_object.abut_list(start_loc, incl_no_coast=True) ]: for support_loc in ( map_object.abut_list(dest_loc, incl_no_coast=True) + map_object.abut_list(dest_loc[:3], incl_no_coast=True)): support_loc = support_loc.upper() # A unit cannot support itself if support_loc[:3] == start_loc[:3]: continue # Making sure the src unit can move to dest # and the support unit can also support to dest for src_unit_type in ['A', 'F']: for support_unit_type in ['A', 'F']: if (map_object.abuts(src_unit_type, start_loc, '-', dest_loc) and map_object.abuts( support_unit_type, support_loc, 'S', dest_loc[:3]) and map_object.is_valid_unit( '%s %s' % (src_unit_type, start_loc)) and map_object.is_valid_unit( '%s %s' % (support_unit_type, support_loc))): orders['S-'].add( '%s %s S %s %s - %s' % (support_unit_type, support_loc, src_unit_type, start_loc, dest_loc[:3])) # Support Move (Convoyed) for nb_fleets in map_object.convoy_paths: # Skipping long-term convoys if nb_fleets > 4: continue for start_loc, fleets, ends in map_object.convoy_paths[nb_fleets]: for dest_loc in ends: for support_loc in map_object.abut_list(dest_loc, incl_no_coast=True): support_loc = support_loc.upper() # A unit cannot support itself if support_loc[:3] == start_loc[:3]: continue # A fleet cannot support if it convoys if support_loc in fleets: continue # Making sure the support unit can also support to dest # And that the support unit is not convoying for support_unit_type in ['A', 'F']: if (map_object.abuts(support_unit_type, support_loc, 'S', dest_loc) and map_object.is_valid_unit( '%s %s' % (support_unit_type, support_loc))): orders['S%d' % nb_fleets].add( '%s %s S A %s - %s' % (support_unit_type, support_loc, start_loc, dest_loc[:3])) # Building the list of final orders final_orders = [PAD_TOKEN, GO_TOKEN, EOS_TOKEN, DRAW_TOKEN] final_orders += [ '<%s>' % power_name for power_name in get_map_powers(map_object) ] final_orders += ['WAIVE'] # Sorting each category for category in categories: category_orders = [ order for order in orders[category] if order not in final_orders ] final_orders += list( sorted( category_orders, key=lambda value: ( value.split()[1], # Sorting by loc value))) # Then alphabetically return final_orders