Exemplo n.º 1
0
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
Exemplo n.º 2
0
    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': ''}
Exemplo n.º 3
0
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