aboutsummaryrefslogtreecommitdiff
path: root/diplomacy/engine/map.py
diff options
context:
space:
mode:
Diffstat (limited to 'diplomacy/engine/map.py')
-rw-r--r--diplomacy/engine/map.py229
1 files changed, 136 insertions, 93 deletions
diff --git a/diplomacy/engine/map.py b/diplomacy/engine/map.py
index 65bcdfa..c23b502 100644
--- a/diplomacy/engine/map.py
+++ b/diplomacy/engine/map.py
@@ -30,84 +30,83 @@ UNDETERMINED, POWER, UNIT, LOCATION, COAST, ORDER, MOVE_SEP, OTHER = 0, 1, 2, 3,
MAP_CACHE = {}
-class Map():
- """ MAP Class
-
- Properties:
- - abbrev: Contains the power abbreviation, otherwise defaults to first letter of PowerName
- e.g. {'ENGLISH': 'E'}
- - abuts_cache: Contains a cache of abuts for ['A,'F'] between all locations for orders ['S', 'C', '-']
- e.g. {(A, PAR, -, MAR): 1, ...}
- - aliases: Contains a dict of all the aliases (e.g. full province name to 3 char)
- e.g. {'EAST': 'EAS', 'STP ( /SC )': 'STP/SC', 'FRENCH': 'FRANCE', 'BUDAPEST': 'BUD', 'NOR': 'NWY', ... }
- - centers: Contains a dict of currently owned supply centers for each player
- e.g. {'RUSSIA': ['MOS', 'SEV', 'STP', 'WAR'], 'FRANCE': ['BRE', 'MAR', 'PAR'], ... }
- - convoy_paths: Contains a list of all possible convoys paths bucketed by number of fleets
- format: {nb of fleets: [(START_LOC, {FLEET LOC}, {DEST LOCS})]}
- - dest_with_coasts: Contains a dictionary of locs with all destinations (incl coasts) that can be reached
- e.g. {'PAR': ['BRE', 'PIC', 'BUR', ...], ...}
- - dummies: Indicates the list of powers that are dummies
- e.g. ['FRANCE', 'ITALY']
- - error: Contains a list of errors that the map generated
- e.g. [''DUPLICATE MAP ALIAS OR POWER: JAPAN']
- - files: Contains a list of files that were loaded (e.g. USES keyword)
- e.g. ['standard.map', 'standard.politics', 'standard.geography', 'standard.military']
- - first_year: Indicates the year where the game is starting.
- e.g. 1901
- - flow: List that contains the seasons with the phases
- e.g. ['SPRING:MOVEMENT,RETREATS', 'FALL:MOVEMENT,RETREATS', 'WINTER:ADJUSTMENTS']
- - flow_sign: Indicate the direction of flow (1 is positive, -1 is negative)
- e.g. 1
- - homes: Contains the list of supply centers where units can be built (i.e. assigned at the beginning)
- e.g. {'RUSSIA': ['MOS', 'SEV', 'STP', 'WAR'], 'FRANCE': ['BRE', 'MAR', 'PAR'], ... }
- - inhabits: List that indicates which power have a INHABITS, HOME, or HOMES line
- e.g. ['FRANCE']
- - keywords: Contains a dict of keywords to parse status files and orders
- e.g. {'BUILDS': 'B', '>': '', 'SC': '/SC', 'REMOVING': 'D', 'WAIVED': 'V', 'ATTACK': '', ... }
- - loc_abut: Contains a adjacency list for each province
- e.g. {'LVP': ['CLY', 'edi', 'IRI', 'NAO', 'WAL', 'yor'], ...}
- - loc_coasts: Contains a mapping of all coasts for every location
- e.g. {'PAR': ['PAR'], 'BUL': ['BUL', 'BUL/EC', 'BUL/SC'], ... }
- - loc_name: Dict that indicates the 3 letter name of each location
- e.g. {'GULF OF LYON': 'LYO', 'BREST': 'BRE', 'BUDAPEST': 'BUD', 'RUHR': 'RUH', ... }
- - loc_type: Dict that indicates if each location is 'WATER', 'COAST', 'LAND', or 'PORT'
- e.g. {'MAO': 'WATER', 'SER': 'LAND', 'SYR': 'COAST', 'MOS': 'LAND', 'VEN': 'COAST', ... }
- - locs: List of 3 letter locations (With coasts)
- e.g. ['ADR', 'AEG', 'ALB', 'ANK', 'APU', 'ARM', 'BAL', 'BAR', 'BEL', 'BER', ... ]
- - name: Name of the map (or full path to a custom map file)
- e.g. 'standard' or '/some/path/to/file.map'
- - own_word: Dict to indicate the word used to refer to people living in each power's country
- e.g. {'RUSSIA': 'RUSSIAN', 'FRANCE': 'FRENCH', 'UNOWNED': 'UNOWNED', 'TURKEY': 'TURKISH', ... }
- - owns: List that indicates which power have a OWNS or CENTERS line
- e.g. ['FRANCE']
- - phase: String to indicate the beginning phase of the map
- e.g. 'SPRING 1901 MOVEMENT'
- - phase_abbrev: Dict to indicate the 1 letter abbreviation for each phase
- e.g. {'A': 'ADJUSTMENTS', 'M': 'MOVEMENT', 'R': 'RETREATS'}
- - pow_name: Dict to indicate the power's name
- e.g. {'RUSSIA': 'RUSSIA', 'FRANCE': 'FRANCE', 'TURKEY': 'TURKEY', 'GERMANY': 'GERMANY', ... }
- - powers: Contains the list of powers (players) in the game
- e.g. ['AUSTRIA', 'ENGLAND', 'FRANCE', 'GERMANY', 'ITALY', 'RUSSIA', 'TURKEY']
- - root_map: Contains the name of the original map file loaded (before the USES keyword are applied)
- A map that is called with MAP is the root_map
- e.g. 'standard'
- - rules: Contains a list of rules used by all variants (for display only)
- e.g. ['RULE_1']
- - scs: Contains a list of all the supply centers in the game
- e.g. ['MOS', 'SEV', 'STP', 'WAR', 'BRE', 'MAR', 'PAR', 'BEL', 'BUL', 'DEN', 'GRE', 'HOL', 'NWY', ... ]
- - seq: [] Contains the sequence of seasons in format 'SEASON_NAME SEASON_TYPE'
- e.g. ['NEWYEAR', 'SPRING MOVEMENT', 'SPRING RETREATS', 'FALL MOVEMENT', 'FALL RETREATS',
- 'WINTER ADJUSTMENTS']
- - unclear: Contains the alias for ambiguous places
- e.g. {'EAST': 'EAS'}
- - unit_names: {} Contains a dict of the unit names
- e.g. {'F': 'FLEET', 'A': 'ARMY'}
- - units: Dict that contains the current position of each unit by power
- e.g. {'FRANCE': ['F BRE', 'A MAR', 'A PAR'], 'RUSSIA': ['A WAR', 'A MOS', 'F SEV', 'F STP/SC'], ... }
- - validated: Boolean to indicate if the map file has been validated
- e.g. 1
- - victory: Indicates the number of supply centers to win the game (>50% required if None)
- e.g. 18
+class Map:
+ """ Map Class
+
+ Properties:
+
+ - **abbrev**: Contains the power abbreviation, otherwise defaults to first letter of PowerName
+ e.g. {'ENGLISH': 'E'}
+ - **abuts_cache**: Contains a cache of abuts for ['A,'F'] between all locations for orders ['S', 'C', '-']
+ e.g. {(A, PAR, -, MAR): 1, ...}
+ - **aliases**: Contains a dict of all the aliases (e.g. full province name to 3 char)
+ e.g. {'EAST': 'EAS', 'STP ( /SC )': 'STP/SC', 'FRENCH': 'FRANCE', 'BUDAPEST': 'BUD', 'NOR': 'NWY', ... }
+ - **centers**: Contains a dict of owned supply centers for each player at the beginning of the map
+ e.g. {'RUSSIA': ['MOS', 'SEV', 'STP', 'WAR'], 'FRANCE': ['BRE', 'MAR', 'PAR'], ... }
+ - **convoy_paths**: Contains a list of all possible convoys paths bucketed by number of fleets
+ format: {nb of fleets: [(START_LOC, {FLEET LOC}, {DEST LOCS})]}
+ - **dest_with_coasts**: Contains a dictionary of locs with all destinations (incl coasts) that can be reached
+ e.g. {'PAR': ['BRE', 'PIC', 'BUR', ...], ...}
+ - **dummies**: Indicates the list of powers that are dummies
+ e.g. ['FRANCE', 'ITALY']
+ - **error**: Contains a list of errors that the map generated
+ e.g. [''DUPLICATE MAP ALIAS OR POWER: JAPAN']
+ - **files**: Contains a list of files that were loaded (e.g. USES keyword)
+ e.g. ['standard.map', 'standard.politics', 'standard.geography', 'standard.military']
+ - **first_year**: Indicates the year where the game is starting.
+ e.g. 1901
+ - **flow**: List that contains the seasons with the phases
+ e.g. ['SPRING:MOVEMENT,RETREATS', 'FALL:MOVEMENT,RETREATS', 'WINTER:ADJUSTMENTS']
+ - **flow_sign**: Indicate the direction of flow (1 is positive, -1 is negative)
+ e.g. 1
+ - **homes**: Contains the list of supply centers where units can be built (i.e. assigned at the beginning)
+ e.g. {'RUSSIA': ['MOS', 'SEV', 'STP', 'WAR'], 'FRANCE': ['BRE', 'MAR', 'PAR'], ... }
+ - **inhabits**: List that indicates which power have a INHABITS, HOME, or HOMES line
+ e.g. ['FRANCE']
+ - **keywords**: Contains a dict of keywords to parse status files and orders
+ e.g. {'BUILDS': 'B', '>': '', 'SC': '/SC', 'REMOVING': 'D', 'WAIVED': 'V', 'ATTACK': '', ... }
+ - **loc_abut**: Contains a adjacency list for each province
+ e.g. {'LVP': ['CLY', 'edi', 'IRI', 'NAO', 'WAL', 'yor'], ...}
+ - **loc_coasts**: Contains a mapping of all coasts for every location
+ e.g. {'PAR': ['PAR'], 'BUL': ['BUL', 'BUL/EC', 'BUL/SC'], ... }
+ - **loc_name**: Dict that indicates the 3 letter name of each location
+ e.g. {'GULF OF LYON': 'LYO', 'BREST': 'BRE', 'BUDAPEST': 'BUD', 'RUHR': 'RUH', ... }
+ - **loc_type**: Dict that indicates if each location is 'WATER', 'COAST', 'LAND', or 'PORT'
+ e.g. {'MAO': 'WATER', 'SER': 'LAND', 'SYR': 'COAST', 'MOS': 'LAND', 'VEN': 'COAST', ... }
+ - **locs**: List of 3 letter locations (With coasts)
+ e.g. ['ADR', 'AEG', 'ALB', 'ANK', 'APU', 'ARM', 'BAL', 'BAR', 'BEL', 'BER', ... ]
+ - **name**: Name of the map (or full path to a custom map file)
+ e.g. 'standard' or '/some/path/to/file.map'
+ - **own_word**: Dict to indicate the word used to refer to people living in each power's country
+ e.g. {'RUSSIA': 'RUSSIAN', 'FRANCE': 'FRENCH', 'UNOWNED': 'UNOWNED', 'TURKEY': 'TURKISH', ... }
+ - **owns**: List that indicates which power have a OWNS or CENTERS line
+ e.g. ['FRANCE']
+ - **phase**: String to indicate the beginning phase of the map
+ e.g. 'SPRING 1901 MOVEMENT'
+ - **phase_abbrev**: Dict to indicate the 1 letter abbreviation for each phase
+ e.g. {'A': 'ADJUSTMENTS', 'M': 'MOVEMENT', 'R': 'RETREATS'}
+ - **pow_name**: Dict to indicate the power's name
+ e.g. {'RUSSIA': 'RUSSIA', 'FRANCE': 'FRANCE', 'TURKEY': 'TURKEY', 'GERMANY': 'GERMANY', ... }
+ - **powers**: Contains the list of powers (players) in the game
+ e.g. ['AUSTRIA', 'ENGLAND', 'FRANCE', 'GERMANY', 'ITALY', 'RUSSIA', 'TURKEY']
+ - **root_map**: Contains the name of the original map file loaded (before the USES keyword are applied)
+ A map that is called with MAP is the root_map. e.g. 'standard'
+ - **rules**: Contains a list of rules used by all variants (for display only)
+ e.g. ['RULE_1']
+ - **scs**: Contains a list of all the supply centers in the game
+ e.g. ['MOS', 'SEV', 'STP', 'WAR', 'BRE', 'MAR', 'PAR', 'BEL', 'BUL', 'DEN', 'GRE', 'HOL', 'NWY', ... ]
+ - **seq**: [] Contains the sequence of seasons in format 'SEASON_NAME SEASON_TYPE'
+ e.g. ['NEWYEAR', 'SPRING MOVEMENT', 'SPRING RETREATS', 'FALL MOVEMENT', 'FALL RETREATS', 'WINTER ADJUSTMENTS']
+ - **unclear**: Contains the alias for ambiguous places
+ e.g. {'EAST': 'EAS'}
+ - **unit_names**: {} Contains a dict of the unit names
+ e.g. {'F': 'FLEET', 'A': 'ARMY'}
+ - **units**: Dict that contains the current position of each unit by power
+ e.g. {'FRANCE': ['F BRE', 'A MAR', 'A PAR'], 'RUSSIA': ['A WAR', 'A MOS', 'F SEV', 'F STP/SC'], ... }
+ - **validated**: Boolean to indicate if the map file has been validated
+ e.g. 1
+ - **victory**: Indicates the number of supply centers to win the game (>50% required if None)
+ e.g. 18
"""
# pylint: disable=too-many-instance-attributes
@@ -119,6 +118,7 @@ class Map():
def __new__(cls, name='standard', use_cache=True):
""" New function - Retrieving object from cache if possible
+
:param name: Name of the map to load
:param use_cache: Boolean flag to indicate we want a blank object that doesn't use cache
"""
@@ -128,6 +128,7 @@ class Map():
def __init__(self, name='standard', use_cache=True):
""" Constructor function
+
:param name: Name of the map to load (or full path to a custom map file)
:param use_cache: Boolean flag to indicate we want a blank object that doesn't use cache
"""
@@ -168,8 +169,18 @@ class Map():
def __str__(self):
return self.name
+ @property
+ def svg_path(self):
+ """ Return path to the SVG file of this map (or None if it does not exist) """
+ for file_name in [self.name + '.svg', self.root_map + '.svg']:
+ svg_path = os.path.join(settings.PACKAGE_DIR, 'maps', 'svg', file_name)
+ if os.path.exists(svg_path):
+ return svg_path
+ return None
+
def validate(self, force=0):
""" Validate that the configuration from a map file is correct
+
:param force: Indicate that we want to force a validation, even if the map is already validated
:return: Nothing
"""
@@ -294,6 +305,7 @@ class Map():
def load(self, file_name=None):
""" Loads a map file from disk
+
:param file_name: Optional. A string representing the file to open. Otherwise, defaults to the map name
:return: Nothing
"""
@@ -716,8 +728,9 @@ class Map():
def add_homes(self, power, homes, reinit):
""" Add new homes (and deletes previous homes if reinit)
+
:param power: Name of power (e.g. ITALY)
- :param homes: List of homes e.g. ['BUR', '-POR', '*ITA', ... ]
+ :param homes: List of homes e.g. ``['BUR', '-POR', '*ITA', ... ]``
:param reinit: Indicates that we want to strip the list of homes before adding
:return: Nothing
"""
@@ -753,6 +766,7 @@ class Map():
def drop(self, place):
""" Drop a place
+
:param place: Name of place to remove
:return: Nothing
"""
@@ -807,6 +821,7 @@ class Map():
def norm_power(self, power):
""" Normalise the name of a power (removes spaces)
+
:param power: Name of power to normalise
:return: Normalised power name
"""
@@ -814,6 +829,7 @@ class Map():
def norm(self, phrase):
""" Normalise a sentence (add spaces before /, replace -+, with ' ', remove .:
+
:param phrase: Phrase to normalise
:return: Normalised sentences
"""
@@ -828,8 +844,9 @@ class Map():
def compact(self, phrase):
""" Compacts a full sentence into a list of short words
+
:param phrase: The full sentence to compact (e.g. 'England: Fleet Western Mediterranean -> Tyrrhenian
- Sea. (*bounce*)')
+ Sea. (*bounce*)')
:return: The compacted phrase in an array (e.g. ['ENGLAND', 'F', 'WES', 'TYS', '|'])
"""
if ':' in phrase:
@@ -849,6 +866,7 @@ class Map():
def alias(self, word):
""" This function is used to replace multi-words with their acronyms
+
:param word: The current list of words to try to shorten
:return: alias, ix - alias is the shorten list of word, ix is the ix of the next non-processed word
"""
@@ -922,12 +940,20 @@ class Map():
def vet(self, word, strict=0):
""" Determines the type of every word in a compacted order phrase
- 0 - Undetermined, 1 - Power, 2 - Unit, 3 - Location, 4 - Coastal location
- 5 - Order, 6 - Move Operator (-=_^), 7 - Non-move separator (|?~) or result (*!?~+)
- :param word: The list of words to vet (e.g. ['A', 'POR', 'S', 'SPA/NC'])
+
+ 0 - Undetermined,
+ 1 - Power,
+ 2 - Unit,
+ 3 - Location,
+ 4 - Coastal location
+ 5 - Order,
+ 6 - Move Operator ``(-=_^)``,
+ 7 - Non-move separator ``(|?~)`` or result ``(*!?~+)``
+
+ :param word: The list of words to vet (e.g. ``['A', 'POR', 'S', 'SPA/NC']``)
:param strict: Boolean to indicate that we want to verify that the words actually exist.
Numbers become negative if they don't exist
- :return: A list of tuple (e.g. [('A', 2), ('POR', 3), ('S', 5), ('SPA/NC', 4)])
+ :return: A list of tuple (e.g. ``[('A', 2), ('POR', 3), ('S', 5), ('SPA/NC', 4)]``)
"""
result = []
for thing in word:
@@ -960,6 +986,7 @@ class Map():
def rearrange(self, word):
""" This function is used to parse commands
+
:param word: The list of words to vet (e.g. ['ENGLAND', 'F', 'WES', 'TYS', '|'])
:return: The list of words in the correct order to be processed (e.g. ['ENGLAND', 'F', 'WES', '-', 'TYS'])
"""
@@ -1070,6 +1097,7 @@ class Map():
def area_type(self, loc):
""" Returns 'WATER', 'COAST', 'PORT', 'LAND', 'SHUT'
+
:param loc: The name of the location to query
:return: Type of the location ('WATER', 'COAST', 'PORT', 'LAND', 'SHUT')
"""
@@ -1078,6 +1106,7 @@ class Map():
def default_coast(self, word):
""" Returns the coast for a fleet move order that can only be to a single coast
(e.g. F GRE-BUL returns F GRE-BUL/SC)
+
:param word: A list of tokens (e.g. ['F', 'GRE', '-', 'BUL'])
:return: The updated list of tokens (e.g. ['F', 'GRE', '-', 'BUL/SC'])
"""
@@ -1096,14 +1125,16 @@ class Map():
def find_coasts(self, loc):
""" Finds all coasts for a given location
+
:param loc: The name of a location (e.g. 'BUL')
:return: Returns the list of all coasts, including the location (e.g. ['BUL', 'BUL/EC', 'BUL/SC']
"""
return self.loc_coasts.get(loc.upper(), [])
def abuts(self, unit_type, unit_loc, order_type, other_loc):
- """ Determines if a order for unit_type from unit_loc to other_loc is adjacent
- Note: This method uses the precomputed cache
+ """ Determines if a order for unit_type from unit_loc to other_loc is adjacent.
+
+ **Note**: This method uses the precomputed cache
:param unit_type: The type of unit ('A' or 'F')
:param unit_loc: The location of the unit ('BUR', 'BUL/EC')
@@ -1120,7 +1151,8 @@ class Map():
def _abuts(self, unit_type, unit_loc, order_type, other_loc):
""" Determines if a order for unit_type from unit_loc to other_loc is adjacent
- Note: This method is used to generate the abuts_cache
+
+ **Note**: This method is used to generate the abuts_cache
:param unit_type: The type of unit ('A' or 'F')
:param unit_loc: The location of the unit ('BUR', 'BUL/EC')
@@ -1186,6 +1218,7 @@ class Map():
def is_valid_unit(self, unit, no_coast_ok=0, shut_ok=0):
""" Determines if a unit and location combination is valid (e.g. 'A BUR') is valid
+
:param unit: The name of the unit with its location (e.g. F SPA/SC)
:param no_coast_ok: Indicates if a coastal location with no coast (e.g. SPA vs SPA/SC) is acceptable
:param shut_ok: Indicates if a impassable country (e.g. Switzerland) is OK
@@ -1209,12 +1242,17 @@ class Map():
def abut_list(self, site, incl_no_coast=False):
""" Returns the adjacency list for the site
+
:param site: The province we want the adjacency list for
:param incl_no_coast: Boolean flag that indicates to also include province without coast if it has coasts
- e.g. will return ['BUL/SC', 'BUL/EC'] if False, and ['bul', 'BUL/SC', 'BUL/EC'] if True
+ e.g. will return ['BUL/SC', 'BUL/EC'] if False, and ['bul', 'BUL/SC', 'BUL/EC'] if True
:return: A list of adjacent provinces
- Note: abuts are returned in mixed cases (lowercase for A only, First capital letter for F only)
+ Note: abuts are returned in **mixed cases**
+
+ - An adjacency that is lowercase (e.g. 'bur') can only be used by an army
+ - An adjacency that starts with a capital letter (e.g. 'Bal') can only be used by a fleet
+ - An adjacency that is uppercase can be used by both an army and a fleet
"""
if site in self.loc_abut:
abut_list = self.loc_abut.get(site, [])
@@ -1229,9 +1267,10 @@ class Map():
def find_next_phase(self, phase, phase_type=None, skip=0):
""" Returns the long name of the phase coming immediately after the phase
+
:param phase: The long name of the current phase (e.g. SPRING 1905 RETREATS)
- :param phase_type: The type of phase we are looking for (e.g. 'M' for Movement, 'R' for Retreats,
- 'A' for Adjust.)
+ :param phase_type: The type of phase we are looking for
+ (e.g. 'M' for Movement, 'R' for Retreats, 'A' for Adjust.)
:param skip: The number of match to skip (e.g. 1 to find not the next phase, but the one after)
:return: The long name of the next phase (e.g. FALL 1905 MOVEMENT)
"""
@@ -1277,9 +1316,10 @@ class Map():
def find_previous_phase(self, phase, phase_type=None, skip=0):
""" Returns the long name of the phase coming immediately prior the phase
+
:param phase: The long name of the current phase (e.g. SPRING 1905 RETREATS)
- :param phase_type: The type of phase we are looking for (e.g. 'M' for Movement, 'R' for Retreats,
- 'A' for Adjust.)
+ :param phase_type: The type of phase we are looking for
+ (e.g. 'M' for Movement, 'R' for Retreats, 'A' for Adjust.)
:param skip: The number of match to skip (e.g. 1 to find not the next phase, but the one after)
:return: The long name of the previous phase (e.g. SPRING 1905 MOVEMENT)
"""
@@ -1332,6 +1372,7 @@ class Map():
def compare_phases(self, phase1, phase2):
""" Compare 2 phases (Strings) and return 1, -1, or 0 to indicate which phase is larger
+
:param phase1: The first phase (e.g. S1901M, FORMING, COMPLETED)
:param phase2: The second phase (e.g. S1901M, FORMING, COMPLETED)
:return: 1 if phase1 > phase2, -1 if phase2 > phase1 otherwise 0 if they are equal
@@ -1378,6 +1419,7 @@ class Map():
@staticmethod
def phase_abbr(phase, default='?????'):
""" Constructs a 5 character representation (S1901M) from a phase (SPRING 1901 MOVEMENT)
+
:param phase: The full phase (e.g. SPRING 1901 MOVEMENT)
:param default: The default value to return in case conversion fails
:return: A 5 character representation of the phase
@@ -1389,6 +1431,7 @@ class Map():
def phase_long(self, phase_abbr, default='?????'):
""" Constructs a full sentence of a phase from a 5 character abbreviation
+
:param phase_abbr: 5 character abbrev. (e.g. S1901M)
:param default: The default value to return in case conversion fails
:return: A full phase description (e.g. SPRING 1901 MOVEMENT)