aboutsummaryrefslogtreecommitdiff
path: root/diplomacy
diff options
context:
space:
mode:
Diffstat (limited to 'diplomacy')
-rw-r--r--diplomacy/engine/game.py100
-rw-r--r--diplomacy/engine/map.py35
-rw-r--r--diplomacy/tests/test_map.py3
-rw-r--r--diplomacy/utils/exceptions.py12
4 files changed, 68 insertions, 82 deletions
diff --git a/diplomacy/engine/game.py b/diplomacy/engine/game.py
index 35344ec..44b2509 100644
--- a/diplomacy/engine/game.py
+++ b/diplomacy/engine/game.py
@@ -868,21 +868,33 @@ class Game(Jsonable):
else:
return {power.name: self.get_orderable_locations(power.name) for power in self.powers.values()}
- def get_order_status(self, power_name=None, unit=None):
+ def get_order_status(self, power_name=None, unit=None, loc=None):
""" Returns a list or a dict representing the order status ('', 'no convoy', 'bounce', 'void', 'cut',
'dislodged', 'disrupted') for orders submitted in the last phase
:param power_name: Optional. If provided (e.g. 'FRANCE') will only return the order status of that
power's orders
:param unit: Optional. If provided (e.g. 'A PAR') will only return that specific unit order status.
+ :param loc: Optional. If provided (e.g. 'PAR') will only return that specific loc order status.
+ Mutually exclusive with unit
:param phase_type: Optional. Returns the results of a specific phase type (e.g. 'M', 'R', or 'A')
:return: If unit is provided a list (e.g. [] or ['void', 'dislodged'])
+ If loc is provided, a couple of unit and list (e.g. ('A PAR', ['void', 'dislodged'])),
+ or loc, [] if unit not found.
If power is provided a dict (e.g. {'A PAR': ['void'], 'A MAR': []})
Otherwise a 2-level dict (e.g. {'FRANCE: {'A PAR': ['void'], 'A MAR': []}, 'ENGLAND': {}, ... }
"""
- # Specific location, returning string
- if unit is not None:
+ # Specific location
+ if unit or loc:
+ assert bool(unit) != bool(loc), 'Required either a unit or a location, not both.'
result_dict = self.result_history.last_value() if self.result_history else {}
- return result_dict[unit][:] if unit in result_dict else []
+ if unit:
+ # Unit given, return list of order status
+ return result_dict[unit][:] if unit in result_dict else []
+ # Loc given, return a couple (unit found, list of order status)
+ for result_unit, result_list in result_dict.items():
+ if result_unit[2:5] == loc[:3]:
+ return result_unit, result_list[:]
+ return loc, []
# Specific power, returning dictionary
if power_name is not None:
@@ -1037,29 +1049,29 @@ class Game(Jsonable):
power_name = power_name.upper()
if not self.has_power(power_name):
- return
+ raise exceptions.MapPowerException('Unknown power %s' % power_name)
if self.is_player_game() and self.role != power_name:
- return
+ raise exceptions.GameRoleException('Player game for %s only accepts orders for this power.' % self.role)
power = self.get_power(power_name)
- if power:
- if not isinstance(orders, list):
- orders = [orders]
- # Remove any empty string from orders.
- orders = [order for order in orders if order]
+ if not isinstance(orders, list):
+ orders = [orders]
- # Setting orders depending on phase type
- if self.phase_type == 'R':
- self._update_retreat_orders(power, orders, expand=expand, replace=replace)
- elif self.phase_type == 'A':
- self._update_adjust_orders(power, orders, expand=expand, replace=replace)
- else:
- self._update_orders(power, orders, expand=expand, replace=replace)
- power.order_is_set = (OrderSettings.ORDER_SET
- if self.get_orders(power.name)
- else OrderSettings.ORDER_SET_EMPTY)
+ # Remove any empty string from orders.
+ orders = [order for order in orders if order]
+
+ # Setting orders depending on phase type
+ if self.phase_type == 'R':
+ self._update_retreat_orders(power, orders, expand=expand, replace=replace)
+ elif self.phase_type == 'A':
+ self._update_adjust_orders(power, orders, expand=expand, replace=replace)
+ else:
+ self._update_orders(power, orders, expand=expand, replace=replace)
+ power.order_is_set = (OrderSettings.ORDER_SET
+ if self.get_orders(power.name)
+ else OrderSettings.ORDER_SET_EMPTY)
def set_wait(self, power_name, wait):
""" Set wait flag for a power.
@@ -2720,7 +2732,7 @@ class Game(Jsonable):
# Score is the number of supply centers owned
for power in self.powers.values():
- score[power] = len([sc for sc in power.centers])
+ score[power] = len(power.centers)
return score
def _determine_win(self, last_year):
@@ -2730,7 +2742,7 @@ class Game(Jsonable):
:return: Nothing
"""
victors, this_year = [], self._calculate_victory_score()
- year_centers = [this_year[x] for x in self.powers.values()]
+ year_centers = list(this_year.values())
# Determining win
for power in self.powers.values():
@@ -3022,7 +3034,7 @@ class Game(Jsonable):
word = self._expand_order([order])
word = self._add_unit_types(word)
- # Add 'R' has order type for Retreat, 'D' for Disband
+ # Add 'R' as order type for Retreat, 'D' for Disband
if word[0] == 'R' and len(word) > 3:
del word[0]
if word[0] in 'RD':
@@ -3073,7 +3085,7 @@ class Game(Jsonable):
for order in adjust:
word = order.split()
if len(word) >= 2 and word[0] != 'VOID':
- power.adjust = [adj_order for adj_order in power.adjust if adj_order.split()[1:2] != word[1:2]]
+ power.adjust = [adj_order for adj_order in power.adjust if adj_order.split()[1] != word[1]]
# Otherwise, marking re-orders as invalid
else:
@@ -3101,8 +3113,7 @@ class Game(Jsonable):
A LON H, F IRI - MAO, A IRI - MAO VIA, A WAL S F LON, A WAL S F MAO - IRI, F NWG C A NWY - EDI
A IRO R MAO, A IRO D, A LON B, F LIV B
"""
- for who, adj in self._distribute_orders(power, orders):
- self._add_retreat_orders(who, adj, expand=expand, replace=replace)
+ self._add_retreat_orders(power, orders, expand=expand, replace=replace)
return self.error
def _add_adjust_orders(self, power, orders, expand=True, replace=True):
@@ -3268,8 +3279,7 @@ class Game(Jsonable):
A LON H, F IRI - MAO, A IRI - MAO VIA, A WAL S F LON, A WAL S F MAO - IRI, F NWG C A NWY - EDI
A IRO R MAO, A IRO D, A LON B, F LIV B
"""
- for who, adj in self._distribute_orders(power, orders):
- self._add_adjust_orders(who, adj, expand=expand, replace=replace)
+ self._add_adjust_orders(power, orders, expand=expand, replace=replace)
return self.error
def _determine_orders(self):
@@ -3306,38 +3316,6 @@ class Game(Jsonable):
for unit in power.units:
self.orders.setdefault(unit, 'H')
- @classmethod
- def _distribute_orders(cls, power, orders, clear=True):
- """ For controlling powers, distribute orders to controlled powers
- :param power: The power instance submitting the orders
- :param orders: The list of orders submitted
- :param clear: Boolean flag to indicate to clear order if NMR/CLEAR submitted
- :return: A list of tuple with (controlled power instance, list of orders for that controlled power instance)
- """
- powers, distributor = [power], {power.name: []}
- cur_power = power
-
- # For each order submitted
- for order in orders:
- # Don't distribute blank order
- word = order.strip().split()
- if not word:
- continue
-
- who = cur_power
-
- # Clearing orders for power
- if (clear
- and len(word) == 1
- and word[0][word[0][:1] in '([':len(word[0]) - (word[0][-1:] in '])')].upper() in ('NMR', 'CLEAR')):
- distributor[who.name] = []
- # Otherwise, distributing order
- else:
- distributor[who.name] += [' '.join(word)]
-
- # Returning a list of tuples with the power instance and their respective orders.
- return [(x, distributor[x.name]) for x in powers]
-
# ====================================================================
# Private Interface - ADJUDICATION Methods
# ====================================================================
diff --git a/diplomacy/engine/map.py b/diplomacy/engine/map.py
index 677a4e7..71e5912 100644
--- a/diplomacy/engine/map.py
+++ b/diplomacy/engine/map.py
@@ -786,9 +786,7 @@ class Map():
:return: Normalised sentences
"""
phrase = phrase.upper().replace('/', ' /').replace(' / ', '')
- for token in '.:':
- phrase = phrase.replace(token, '')
- for token in '-+,':
+ for token in '.:-+,':
phrase = phrase.replace(token, ' ')
for token in '|*?!~()[]=_^':
phrase = phrase.replace(token, ' {} '.format(token))
@@ -802,6 +800,13 @@ class Map():
Sea. (*bounce*)')
:return: The compacted phrase in an array (e.g. ['ENGLAND', 'F', 'WES', 'TYS', '|'])
"""
+ if ':' in phrase:
+ # Check if first part of phrase (before colon) is a power, and remove it if that's the case.
+ index_colon = phrase.index(':')
+ first_part = phrase[:index_colon]
+ result = self.vet(self.compact(first_part))
+ if len(result) == 1 and result[0][1] == POWER:
+ phrase = phrase[(index_colon + 1):]
word, result = self.norm(phrase).split(), []
while word:
alias, i = self.alias(word)
@@ -852,29 +857,37 @@ class Map():
# Concatenate coasts
if i == len(word):
- return alias, i
- if alias[:1] != '/' and ' ' not in alias:
+ return self._resolve_unclear(alias), i
+ if alias[0] != '/' and ' ' not in alias:
alias2, j = self.alias(word[i:])
- if alias2[:1] != '/' or ' ' in alias2:
- return alias, i
+ if alias2[0] != '/' or ' ' in alias2:
+ return self._resolve_unclear(alias), i
elif alias[-2:] == ' \\':
alias2, j = self.alias(word[i:])
- if alias2[:1] == '/' or ' ' in alias2:
+ if alias2[0] == '/' or ' ' in alias2:
return alias, i
alias, alias2 = alias2, alias[:-2]
else:
- return alias, i
+ return self._resolve_unclear(alias), i
# Check if the location is also an ambiguous power name
# and replace with its other name if that's the case
- if alias in self.powers and alias in self.unclear:
- alias = self.unclear[alias]
+ alias = self._resolve_unclear(alias)
# Check if the coast is mapped to another coast
if alias + ' ' + alias2 in self.aliases:
return self.aliases[alias + ' ' + alias2], i + j
return alias + alias2, i + j
+ def _resolve_unclear(self, alias):
+ """ Check if given aliases string is an unclear power name.
+ If that's the case, return other name associated to this alias.
+ Otherwise, return alias unchanged,
+ """
+ if alias in self.powers and alias in self.unclear:
+ alias = self.unclear[alias]
+ return alias
+
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
diff --git a/diplomacy/tests/test_map.py b/diplomacy/tests/test_map.py
index 507efae..6a12f71 100644
--- a/diplomacy/tests/test_map.py
+++ b/diplomacy/tests/test_map.py
@@ -55,8 +55,9 @@ def test_drop():
def test_compact():
""" Tests map.compact """
this_map = deepcopy(Map())
+ # Power name at top of string is removed by Map.compact().
assert this_map.compact('England: Fleet Western Mediterranean -> Tyrrhenian Sea. (*bounce*)') \
- == ['ENGLAND', 'F', 'WES', 'TYS', '|']
+ == ['F', 'WES', 'TYS', '|']
def test_norm_power():
""" Tests map.norm_power """
diff --git a/diplomacy/utils/exceptions.py b/diplomacy/utils/exceptions.py
index 4d564a3..5cf4384 100644
--- a/diplomacy/utils/exceptions.py
+++ b/diplomacy/utils/exceptions.py
@@ -101,6 +101,9 @@ class GameIdException(ResponseException):
class GameJoinRoleException(ResponseException):
""" A token can have only one role inside a game: player, observer or omniscient. """
+class GameRoleException(ResponseException):
+ """ Game role does not accepts this action. """
+
class GameMasterTokenException(ResponseException):
""" Invalid token for master operations. """
@@ -145,17 +148,11 @@ class MapPowerException(ResponseException):
def __init__(self, power_name):
super(MapPowerException, self).__init__('Invalid map power %s' % power_name)
-class ServerDataDirException(ResponseException):
- """ No data directory available in server folder. """
-
class FolderException(ResponseException):
""" Given folder not available in server. """
def __init__(self, folder_path):
super(FolderException, self).__init__('Given folder not available in server: %s' % folder_path)
-class ServerGameDirException(ResponseException):
- """ No games directory available in server/data folder. """
-
class ServerRegistrationException(ResponseException):
""" Registration currently not allowed on this server. """
@@ -168,9 +165,6 @@ class UserException(ResponseException):
class PasswordException(ResponseException):
""" Password must not be empty. """
-class VoteCreationException(ResponseException):
- """ Only either a player or a game master for a game with at least 1 player can create a vote. """
-
class ServerDirException(ResponseException):
""" Error with working folder. """