mirror of
https://github.com/GoodStartLabs/AI_Diplomacy.git
synced 2026-04-19 12:58:09 +00:00
828 lines
32 KiB
Python
828 lines
32 KiB
Python
# ==============================================================================
|
|
# Copyright (C) 2019 - Philip Paquette
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify it under
|
|
# the terms of the GNU Affero General Public License as published by the Free
|
|
# Software Foundation, either version 3 of the License, or (at your option) any
|
|
# later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
# details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License along
|
|
# with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
# ==============================================================================
|
|
""" Daide Clauses - Contains clauses that can be used to build / parse requests and responses """
|
|
from abc import ABCMeta, abstractmethod
|
|
import logging
|
|
from diplomacy.daide import tokens
|
|
from diplomacy.daide.tokens import Token
|
|
|
|
# Constants
|
|
LOGGER = logging.getLogger(__name__)
|
|
|
|
def break_next_group(daide_bytes):
|
|
""" If the next token is a parenthesis, finds its matching closing parenthesis and returns a tuple of the items
|
|
between parentheses and the items after the closing parenthesis.
|
|
e.g. bytes for (ENG AMY PAR) MTO NWY would return --> (ENG AMY PAR) + MTO NWY
|
|
e.g. bytes for ENG AMY PAR would return -> '' + ENG AMY PAR since the byte array does not start with a "("
|
|
|
|
:return: A tuple consisting of the parenthesis group and the remaining bytes after the group
|
|
or an empty byte array and the entire byte array if the byte array does not start with a parenthesis
|
|
"""
|
|
if not daide_bytes:
|
|
return b'', b''
|
|
|
|
# Finding the matching closing parenthesis
|
|
pos = 0
|
|
parentheses_level = 0
|
|
while True:
|
|
if daide_bytes[pos:pos + 2] == bytes(tokens.OPE_PAR):
|
|
parentheses_level += 1
|
|
elif daide_bytes[pos:pos + 2] == bytes(tokens.CLO_PAR):
|
|
parentheses_level -= 1
|
|
if parentheses_level <= 0:
|
|
break
|
|
if pos >= len(daide_bytes): # Parentheses don't match - Not returning group
|
|
pos = 0
|
|
break
|
|
pos += 2
|
|
|
|
# Returning
|
|
return (daide_bytes[:pos + 2], daide_bytes[pos + 2:]) if pos else (None, daide_bytes)
|
|
|
|
def add_parentheses(daide_bytes):
|
|
""" Add parentheses to a list of bytes """
|
|
if not daide_bytes:
|
|
return daide_bytes
|
|
return bytes(tokens.OPE_PAR) + daide_bytes + bytes(tokens.CLO_PAR)
|
|
|
|
def strip_parentheses(daide_bytes):
|
|
""" Removes parentheses from the DAIDE bytes and returns the inner content.
|
|
The first and last token are expected to be parentheses.
|
|
"""
|
|
assert daide_bytes[:2] == bytes(tokens.OPE_PAR), 'Expected bytes to start with "("'
|
|
assert daide_bytes[-2:] == bytes(tokens.CLO_PAR), 'Expected bytes to end wth ")"'
|
|
return daide_bytes[2:-2]
|
|
|
|
def parse_bytes(clause_constructor, daide_bytes, on_error='raise'):
|
|
""" Creates a clause object from a string of bytes
|
|
|
|
:param clause_constructor: The type of clause to build
|
|
:param daide_bytes: The bytes to use to build this clause
|
|
:param on_error: The action to take when an error is encountered ('raise', 'warn', 'ignore')
|
|
:return: A tuple of 1) the clause object, and 2) the remaining (unparsed) bytes
|
|
"""
|
|
assert on_error in ('raise', 'warn', 'ignore'), 'Valid values for error are "raise", "warn", "ignore"'
|
|
clause = clause_constructor()
|
|
daide_bytes = clause.from_bytes(daide_bytes, on_error=on_error)
|
|
if not clause.is_valid:
|
|
return None, daide_bytes
|
|
return clause, daide_bytes
|
|
|
|
def parse_string(clause_constructor, string, on_error='raise'):
|
|
""" Creates a clause object from a string
|
|
|
|
:param clause_constructor: The type of clause to build
|
|
:param string: The string to use to build this clause
|
|
:param on_error: The action to take when an error is encountered ('raise', 'warn', 'ignore')
|
|
:return: The clause object
|
|
"""
|
|
assert on_error in ('raise', 'warn', 'ignore'), 'Valid values for error are "raise", "warn", "ignore"'
|
|
clause = clause_constructor()
|
|
clause.from_string(string, on_error=on_error)
|
|
if not clause.is_valid:
|
|
return None
|
|
return clause
|
|
|
|
class AbstractClause(metaclass=ABCMeta):
|
|
""" Abstract version of a DAIDE clause """
|
|
def __init__(self):
|
|
""" Constructor """
|
|
self._is_valid = True
|
|
|
|
@property
|
|
def is_valid(self):
|
|
""" Indicates if the clause is valid (no errors were triggered) """
|
|
return self._is_valid
|
|
|
|
@abstractmethod
|
|
def __bytes__(self):
|
|
""" Define the DAIDE bytes representation """
|
|
raise NotImplementedError()
|
|
|
|
@abstractmethod
|
|
def from_bytes(self, daide_bytes, on_error='raise'):
|
|
""" Builds the clause from a byte array
|
|
|
|
:param daide_bytes: The bytes to use to build the clause
|
|
:param on_error: The action to take when an error is encountered ('raise', 'warn', 'ignore')
|
|
:return: The remaining (unparsed) bytes
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
@abstractmethod
|
|
def from_string(self, string, on_error='raise'):
|
|
""" Builds the clause from a string
|
|
|
|
:param string: The string to use to build the clause
|
|
:param on_error: The action to take when an error is encountered ('raise', 'warn', 'ignore')
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
def error(self, on_error, message=''):
|
|
""" Performs the error action
|
|
|
|
:param on_error: The action to take when an error is encountered ('raise', 'warn', 'ignore')
|
|
:param message: The message to display
|
|
"""
|
|
assert on_error in ('raise', 'warn', 'ignore'), 'Valid values for error are "raise", "warn", "ignore"'
|
|
if on_error == 'raise':
|
|
raise RuntimeError(message)
|
|
if on_error == 'warn':
|
|
LOGGER.warning(message)
|
|
self._is_valid = False
|
|
|
|
class SingleToken(AbstractClause):
|
|
""" Extracts a single token (e.g. NME) """
|
|
def __init__(self):
|
|
""" Constructor """
|
|
super(SingleToken, self).__init__()
|
|
self._bytes = b''
|
|
self._str = ''
|
|
|
|
def __bytes__(self):
|
|
""" Define the DAIDE bytes representation """
|
|
return self._bytes
|
|
|
|
def __str__(self):
|
|
""" Return the Diplomacy str """
|
|
return self._str
|
|
|
|
def from_bytes(self, daide_bytes, on_error='raise'):
|
|
""" Builds the clause from a byte array
|
|
|
|
:param daide_bytes: The bytes to use to build the clause
|
|
:param on_error: The action to take when an error is encountered ('raise', 'warn', 'ignore')
|
|
:return: The remaining (unparsed) bytes
|
|
"""
|
|
token_bytes, remaining_bytes = daide_bytes[:2], daide_bytes[2:]
|
|
|
|
# Not enough bytes to get a token
|
|
if not token_bytes:
|
|
self.error(on_error, 'At least 2 bytes are required to build a token.')
|
|
return remaining_bytes
|
|
|
|
# Getting the token
|
|
self._bytes = token_bytes
|
|
self._str = str(Token(from_bytes=token_bytes))
|
|
return remaining_bytes
|
|
|
|
def from_string(self, string, on_error='raise'):
|
|
""" Builds the clause from a string
|
|
|
|
:param string: The string to use to build this clause
|
|
:param on_error: The action to take when an error is encountered ('raise', 'warn', 'ignore')
|
|
"""
|
|
# Not enough bytes to get a token
|
|
if not string:
|
|
self.error(on_error, '`string` cannot be empty or None')
|
|
return
|
|
|
|
# Getting the token
|
|
self._bytes = bytes(Token(from_str=string))
|
|
self._str = string
|
|
|
|
class Power(SingleToken):
|
|
""" Each clause is a power
|
|
Syntax: ENG
|
|
"""
|
|
_alias_from_bytes = {'AUS': 'AUSTRIA',
|
|
'ENG': 'ENGLAND',
|
|
'FRA': 'FRANCE',
|
|
'GER': 'GERMANY',
|
|
'ITA': 'ITALY',
|
|
'RUS': 'RUSSIA',
|
|
'TUR': 'TURKEY'}
|
|
_alias_from_string = {value: key for key, value in _alias_from_bytes.items()}
|
|
|
|
def from_bytes(self, daide_bytes, on_error='raise'):
|
|
""" Builds the clause from a byte array
|
|
|
|
:param daide_bytes: The bytes to use to build the clause
|
|
:param on_error: The action to take when an error is encountered ('raise', 'warn', 'ignore')
|
|
:return: The remaining (unparsed) bytes
|
|
"""
|
|
remaining_bytes = super(Power, self).from_bytes(daide_bytes, on_error)
|
|
self._str = self._alias_from_bytes.get(self._str, self._str)
|
|
return remaining_bytes
|
|
|
|
def from_string(self, string, on_error='raise'):
|
|
""" Builds the clause from a string
|
|
|
|
:param string: The string to use to build this clause
|
|
:param on_error: The action to take when an error is encountered ('raise', 'warn', 'ignore')
|
|
"""
|
|
str_power = self._alias_from_string.get(string, string)
|
|
super(Power, self).from_string(str_power, on_error)
|
|
|
|
class String(AbstractClause):
|
|
""" A string contained between parentheses
|
|
Syntax (Text)
|
|
"""
|
|
def __init__(self):
|
|
""" Constructor """
|
|
super(String, self).__init__()
|
|
self._bytes = b''
|
|
self._str = ''
|
|
|
|
def __bytes__(self):
|
|
""" Define the DAIDE bytes representation """
|
|
return self._bytes
|
|
|
|
def __str__(self):
|
|
""" Return the Diplomacy str """
|
|
return self._str
|
|
|
|
def from_bytes(self, daide_bytes, on_error='raise'):
|
|
""" Builds the clause from a byte array
|
|
|
|
:param daide_bytes: The bytes to use to build the clause
|
|
:param on_error: The action to take when an error is encountered ('raise', 'warn', 'ignore')
|
|
:return: The remaining (unparsed) bytes
|
|
"""
|
|
str_group_bytes, remaining_bytes = break_next_group(daide_bytes)
|
|
|
|
# Can't find the string
|
|
if not str_group_bytes:
|
|
self.error(on_error, 'Unable to find a set of parentheses to extract the string clause.')
|
|
return daide_bytes
|
|
|
|
# Extract its content
|
|
nb_bytes = len(str_group_bytes)
|
|
self._bytes = str_group_bytes
|
|
self._str = ''.join([str(Token(from_bytes=str_group_bytes[pos:pos + 2])) for pos in range(2, nb_bytes - 2, 2)])
|
|
return remaining_bytes
|
|
|
|
def from_string(self, string, on_error='raise'):
|
|
""" Builds the clause from a string
|
|
|
|
:param string: The string to use to build the clause
|
|
:param on_error: The action to take when an error is encountered ('raise', 'warn', 'ignore')
|
|
"""
|
|
self._bytes = add_parentheses(b''.join([bytes(Token(from_str=char)) for char in string]))
|
|
self._str = string
|
|
|
|
class Number(AbstractClause):
|
|
""" A number contained between parentheses
|
|
Syntax: Number
|
|
"""
|
|
def __init__(self):
|
|
""" Constructor """
|
|
super(Number, self).__init__()
|
|
self._bytes = b''
|
|
self._int = 0
|
|
|
|
def __bytes__(self):
|
|
""" Define the DAIDE bytes representation """
|
|
return self._bytes
|
|
|
|
def __str__(self):
|
|
""" Return the Diplomacy str """
|
|
return str(self._int)
|
|
|
|
def __int__(self):
|
|
""" Return the Diplomacy int """
|
|
return self._int
|
|
|
|
def from_bytes(self, daide_bytes, on_error='raise'):
|
|
""" Builds the clause from a byte array
|
|
|
|
:param daide_bytes: The bytes to use to build the clause
|
|
:param on_error: The action to take when an error is encountered ('raise', 'warn', 'ignore')
|
|
:return: The remaining (unparsed) bytes
|
|
"""
|
|
if not daide_bytes:
|
|
self.error(on_error, 'Expected at least 1 byte to parse a number')
|
|
return daide_bytes
|
|
|
|
number_bytes, remaining_bytes = daide_bytes[:2], daide_bytes[2:]
|
|
number_token = Token(from_bytes=number_bytes)
|
|
if not tokens.is_integer_token(number_token):
|
|
self.error(on_error, 'The token is not an integer. Got %s' % number_token)
|
|
return daide_bytes
|
|
|
|
# Extract its content
|
|
self._bytes = number_bytes
|
|
self._int = int(number_token)
|
|
return remaining_bytes
|
|
|
|
def from_string(self, string, on_error='raise'):
|
|
""" Builds the clause from a string
|
|
|
|
:param string: The string to use to build the clause
|
|
:param on_error: The action to take when an error is encountered ('raise', 'warn', 'ignore')
|
|
"""
|
|
self._bytes = bytes(Token(from_int=int(string)))
|
|
self._int = int(string)
|
|
|
|
class Province(AbstractClause):
|
|
""" Each clause is an province token
|
|
Syntax:
|
|
|
|
- ADR
|
|
- (STP ECS)
|
|
"""
|
|
_alias_from_bytes = {'ECS': '/EC',
|
|
'NCS': '/NC',
|
|
'SCS': '/SC',
|
|
'WCS': '/WC',
|
|
'ECH': 'ENG',
|
|
'GOB': 'BOT',
|
|
'GOL': 'LYO'}
|
|
_alias_from_string = {value: key for key, value in _alias_from_bytes.items()}
|
|
|
|
def __init__(self):
|
|
""" Constructor """
|
|
super(Province, self).__init__()
|
|
self._bytes = b''
|
|
self._str = ''
|
|
|
|
def __bytes__(self):
|
|
""" Define the DAIDE bytes representation """
|
|
return self._bytes
|
|
|
|
def __str__(self):
|
|
""" Return the Diplomacy str """
|
|
return self._str
|
|
|
|
def from_bytes(self, daide_bytes, on_error='raise'):
|
|
""" Builds the clause from a byte array
|
|
|
|
:param daide_bytes: The bytes to use to build the clause
|
|
:param on_error: The action to take when an error is encountered ('raise', 'warn', 'ignore')
|
|
:return: The remaining (unparsed) bytes
|
|
"""
|
|
province_group_bytes, remaining_bytes = break_next_group(daide_bytes)
|
|
|
|
# Is a province with coast
|
|
# Syntax (STP NCS)
|
|
if province_group_bytes:
|
|
self._bytes = province_group_bytes
|
|
|
|
province_group_bytes = strip_parentheses(province_group_bytes)
|
|
province, province_group_bytes = parse_bytes(SingleToken, province_group_bytes, on_error=on_error)
|
|
coast, province_group_bytes = parse_bytes(SingleToken, province_group_bytes, on_error=on_error)
|
|
|
|
if province_group_bytes:
|
|
self.error(on_error, '{} bytes remaining. Province is malformed'.format(len(province_group_bytes)))
|
|
return daide_bytes
|
|
|
|
str_province = self._alias_from_bytes.get(str(province), str(province))
|
|
str_coast = self._alias_from_bytes.get(str(coast), str(coast))
|
|
self._str = str_province + str_coast
|
|
|
|
# Is a province with no coast
|
|
# Syntax: ADR
|
|
else:
|
|
province, remaining_bytes = parse_bytes(SingleToken, remaining_bytes, on_error=on_error)
|
|
self._bytes = bytes(province)
|
|
self._str = self._alias_from_bytes.get(str(province), str(province))
|
|
|
|
return remaining_bytes
|
|
|
|
def from_string(self, string, on_error='raise'):
|
|
""" Builds the clause from a string
|
|
|
|
:param string: The string to use to build the clause
|
|
:param on_error: The action to take when an error is encountered ('raise', 'warn', 'ignore')
|
|
"""
|
|
province, coast = string.split('/') if '/' in string else [string, None]
|
|
|
|
# Province with coast
|
|
# Syntax: (STP NCS)
|
|
if province and coast:
|
|
str_province = self._alias_from_string.get(province, province)
|
|
str_coast = self._alias_from_string.get('/' + coast, '')
|
|
|
|
if not str_coast:
|
|
self.error(on_error, 'Unknown coast "%s".' % '/' + coast)
|
|
return
|
|
|
|
self._str = str_province + str_coast
|
|
self._bytes = add_parentheses(bytes(Token(from_str=str_province)) + bytes(Token(from_str=str_coast)))
|
|
|
|
# Province without coast
|
|
# Syntax: ADR
|
|
else:
|
|
str_province = self._alias_from_string.get(string, string)
|
|
self._str = str_province
|
|
self._bytes = bytes(Token(from_str=str_province))
|
|
|
|
class Turn(AbstractClause):
|
|
""" Each clause is a Turn
|
|
Syntax: (SPR 1901)
|
|
"""
|
|
_alias_from_bytes = {'AUT': 'F.R',
|
|
'FAL': 'F.M',
|
|
'SPR': 'S.M',
|
|
'SUM': 'S.R',
|
|
'WIN': 'W.A'}
|
|
_alias_from_string = {value: key for key, value in _alias_from_bytes.items()}
|
|
|
|
def __init__(self):
|
|
""" Constructor """
|
|
super(Turn, self).__init__()
|
|
self._bytes = b''
|
|
self._str = ''
|
|
|
|
def __bytes__(self):
|
|
""" Define the DAIDE bytes representation """
|
|
return self._bytes
|
|
|
|
def __str__(self):
|
|
""" Return the Diplomacy str """
|
|
return self._str
|
|
|
|
def from_bytes(self, daide_bytes, on_error='raise'):
|
|
""" Builds the clause from a byte array
|
|
|
|
:param daide_bytes: The bytes to use to build the clause
|
|
:param on_error: The action to take when an error is encountered ('raise', 'warn', 'ignore')
|
|
:return: The remaining (unparsed) bytes
|
|
"""
|
|
turn_group_bytes, remaining_bytes = break_next_group(daide_bytes)
|
|
|
|
# Can't find the order
|
|
if not turn_group_bytes:
|
|
self.error(on_error, 'Unable to find a set of parentheses to extract the turn clause.')
|
|
return daide_bytes
|
|
|
|
self._bytes = turn_group_bytes
|
|
|
|
turn_group_bytes = strip_parentheses(turn_group_bytes)
|
|
season, turn_group_bytes = parse_bytes(SingleToken, turn_group_bytes, on_error=on_error)
|
|
year, turn_group_bytes = parse_bytes(Number, turn_group_bytes, on_error=on_error)
|
|
|
|
if turn_group_bytes:
|
|
self.error(on_error, '{} bytes remaining. Turn is malformed'.format(len(turn_group_bytes)))
|
|
return daide_bytes
|
|
|
|
season_alias = self._alias_from_bytes.get(str(season), str(season))
|
|
self._str = ''.join([season_alias[0], str(year), season_alias[-1]])
|
|
return remaining_bytes
|
|
|
|
def from_string(self, string, on_error='raise'):
|
|
""" Builds the clause from a string
|
|
|
|
:param string: The string to use to build the clause
|
|
:param on_error: The action to take when an error is encountered ('raise', 'warn', 'ignore')
|
|
"""
|
|
str_season = self._alias_from_string.get('%s.%s' % (string[0], string[-1]), '')
|
|
str_year = string[1:-1]
|
|
|
|
if not str_season or not str_year:
|
|
self.error(on_error, 'Unknown season and/or year "%s".' % string)
|
|
return
|
|
|
|
self._str = string
|
|
self._bytes = add_parentheses(bytes(Token(from_str=str_season)) + bytes(Token(from_int=int(str_year))))
|
|
|
|
class UnitType(SingleToken):
|
|
""" Each clause is an season token
|
|
Syntax: AMY
|
|
"""
|
|
_alias_from_bytes = {'AMY': 'A',
|
|
'FLT': 'F'}
|
|
_alias_from_string = {value: key for key, value in _alias_from_bytes.items()}
|
|
|
|
def from_bytes(self, daide_bytes, on_error='raise'):
|
|
""" Builds the clause from a byte array
|
|
|
|
:param daide_bytes: The bytes to use to build the clause
|
|
:param on_error: The action to take when an error is encountered ('raise', 'warn', 'ignore')
|
|
:return: The remaining (unparsed) bytes
|
|
"""
|
|
remaining_bytes = super(UnitType, self).from_bytes(daide_bytes, on_error)
|
|
self._str = self._alias_from_bytes.get(self._str, self._str)
|
|
return remaining_bytes
|
|
|
|
def from_string(self, string, on_error='raise'):
|
|
""" Builds the clause from a string
|
|
|
|
:param string: The string to use to build this clause
|
|
:param on_error: The action to take when an error is encountered ('raise', 'warn', 'ignore')
|
|
"""
|
|
str_unit_type = self._alias_from_string.get(string, '')
|
|
if not str_unit_type:
|
|
self.error(on_error, 'Unknown unit type "%s"' % string)
|
|
self._str = string
|
|
self._bytes = bytes(Token(from_str=str_unit_type))
|
|
|
|
class Unit(AbstractClause):
|
|
""" Each clause is an army or fleet
|
|
Syntax: (ITA AMY TUN)
|
|
"""
|
|
_UNK = 'UNO' # Unknown power
|
|
|
|
def __init__(self):
|
|
""" Constructor """
|
|
super(Unit, self).__init__()
|
|
self._bytes = b''
|
|
self._str = ''
|
|
self._power_name = None
|
|
|
|
@property
|
|
def power_name(self):
|
|
""" The power name """
|
|
return self._power_name
|
|
|
|
def __bytes__(self):
|
|
""" Define the DAIDE bytes representation """
|
|
return self._bytes
|
|
|
|
def __str__(self):
|
|
""" Return the Diplomacy str """
|
|
return self._str
|
|
|
|
def from_bytes(self, daide_bytes, on_error='raise'):
|
|
""" Builds the clause from a byte array
|
|
|
|
:param daide_bytes: The bytes to use to build the clause
|
|
:param on_error: The action to take when an error is encountered ('raise', 'warn', 'ignore')
|
|
:return: The remaining (unparsed) bytes
|
|
"""
|
|
unit_group_bytes, remaining_bytes = break_next_group(daide_bytes)
|
|
|
|
# Can't find the order
|
|
if not unit_group_bytes:
|
|
self.error(on_error, 'Unable to find a set of parentheses to extract the order clause.')
|
|
return daide_bytes
|
|
|
|
# Extract its content
|
|
self._bytes = unit_group_bytes
|
|
|
|
unit_group_bytes = strip_parentheses(unit_group_bytes)
|
|
power, unit_group_bytes = parse_bytes(Power, unit_group_bytes, on_error=on_error)
|
|
unit_type, unit_group_bytes = parse_bytes(UnitType, unit_group_bytes, on_error=on_error)
|
|
province, unit_group_bytes = parse_bytes(Province, unit_group_bytes, on_error=on_error)
|
|
|
|
if unit_group_bytes:
|
|
self.error(on_error, '{} bytes remaining. Order is malformed'.format(len(unit_group_bytes)))
|
|
return daide_bytes
|
|
|
|
self._power_name = str(power)
|
|
self._str = ' '.join([str(unit_type), str(province)])
|
|
return remaining_bytes
|
|
|
|
def from_string(self, string, on_error='raise'):
|
|
""" Builds the clause from a string
|
|
|
|
:param string: The string to use to build the clause
|
|
:param on_error: The action to take when an error is encountered ('raise', 'warn', 'ignore')
|
|
"""
|
|
words = string.split()
|
|
|
|
# Checking number of words available
|
|
if len(words) == 2:
|
|
str_power = self._UNK
|
|
str_unit_type, str_province = words
|
|
elif len(words) == 3:
|
|
str_power, str_unit_type, str_province = words
|
|
else:
|
|
self.error(on_error, 'Expected 2 or 3 words (e.g. "A PAR" or "FRANCE A PAR").')
|
|
return
|
|
|
|
# Parsing
|
|
power = parse_string(Power, str_power, on_error=on_error)
|
|
unit_type = parse_string(UnitType, str_unit_type, on_error=on_error)
|
|
province = parse_string(Province, str_province, on_error=on_error)
|
|
|
|
self._power_name = str(power)
|
|
self._str = ' '.join([str(unit_type), str(province)])
|
|
self._bytes = add_parentheses(bytes(power) + bytes(unit_type) + bytes(province))
|
|
|
|
class OrderType(SingleToken):
|
|
""" Each clause is an order token
|
|
Syntax: SUB
|
|
"""
|
|
_alias_from_bytes = {'HLD': 'H',
|
|
'MTO': '-',
|
|
'SUP': 'S',
|
|
'CVY': 'C',
|
|
'CTO': '-',
|
|
'VIA': 'VIA',
|
|
'RTO': 'R',
|
|
'DSB': 'D',
|
|
'BLD': 'B',
|
|
'REM': 'D',
|
|
'WVE': 'WAIVE'}
|
|
_alias_from_string = {'H': 'HLD',
|
|
'-': 'MTO',
|
|
'S': 'SUP',
|
|
'C': 'CVY',
|
|
'VIA': 'VIA',
|
|
'R': 'RTO',
|
|
'D': 'REM',
|
|
'B': 'BLD',
|
|
'WAIVE': 'WVE'}
|
|
|
|
def from_bytes(self, daide_bytes, on_error='raise'):
|
|
""" Builds the clause from a byte array
|
|
|
|
:param daide_bytes: The bytes to use to build the clause
|
|
:param on_error: The action to take when an error is encountered ('raise', 'warn', 'ignore')
|
|
:return: The remaining (unparsed) bytes
|
|
"""
|
|
remaining_bytes = super(OrderType, self).from_bytes(daide_bytes, on_error)
|
|
self._str = self._alias_from_bytes.get(self._str, self._str)
|
|
return remaining_bytes
|
|
|
|
def from_string(self, string, on_error='raise'):
|
|
""" Builds the clause from a string
|
|
|
|
:param string: The string to use to build this clause
|
|
:param on_error: The action to take when an error is encountered ('raise', 'warn', 'ignore')
|
|
"""
|
|
str_order_type = self._alias_from_string.get(string, string)
|
|
super(OrderType, self).from_string(str_order_type, on_error)
|
|
|
|
def parse_order_to_bytes(phase_type, order_split):
|
|
""" Builds an order clause from a byte array
|
|
|
|
:param phase_type: The game phase
|
|
:param order_split: An instance of diplomacy.utils.subject_split.OrderSplit
|
|
:return: The order clause's bytes
|
|
"""
|
|
buffer = []
|
|
|
|
# FRANCE WAIVE
|
|
if len(order_split) == 1:
|
|
words = order_split.order_type.split()
|
|
buffer.append(parse_string(Power, words.pop(0)))
|
|
buffer.append(parse_string(OrderType, words.pop(0)))
|
|
else:
|
|
buffer.append(parse_string(Unit, order_split.unit))
|
|
|
|
# FRANCE F IRI [-] MAO
|
|
# FRANCE A IRI [-] MAO VIA
|
|
if order_split.order_type == '-':
|
|
# FRANCE A IRI - MAO VIA
|
|
if order_split.via_flag:
|
|
buffer.append(Token(tokens.CTO))
|
|
else:
|
|
buffer.append(Token(tokens.MTO))
|
|
# FRANCE A IRO [D]
|
|
elif order_split.order_type == 'D':
|
|
if phase_type == 'R':
|
|
buffer.append(Token(tokens.DSB))
|
|
elif phase_type == 'A':
|
|
buffer.append(Token(tokens.REM))
|
|
# FRANCE A LON [H]
|
|
# FRANCE A WAL [S] FRANCE F LON
|
|
# FRANCE A WAL [S] FRANCE F MAO - IRI
|
|
# FRANCE F NWG [C] FRANCE A NWY - EDI
|
|
# FRANCE A IRO [R] MAO
|
|
# FRANCE A LON [B]
|
|
# FRANCE F LIV [B]
|
|
else:
|
|
buffer.append(parse_string(OrderType, order_split.order_type))
|
|
|
|
# FRANCE A WAL S [FRANCE F LON]
|
|
# FRANCE A WAL S [FRANCE F MAO] - IRI
|
|
# FRANCE F NWG C [FRANCE A NWY] - EDI
|
|
if order_split.supported_unit:
|
|
buffer.append(parse_string(Unit, order_split.supported_unit))
|
|
|
|
# FRANCE A WAL S FRANCE F MAO [- IRI]
|
|
# FRANCE F NWG C FRANCE A NWY [- EDI]
|
|
if order_split.support_order_type:
|
|
# FRANCE A WAL S FRANCE F MAO - IRI
|
|
if order_split.order_type == 'S':
|
|
buffer.append(Token(tokens.MTO))
|
|
buffer.append(parse_string(Province, order_split.destination[:3]))
|
|
else:
|
|
buffer.append(Token(tokens.CTO))
|
|
buffer.append(parse_string(Province, order_split.destination))
|
|
# FRANCE F IRI - [MAO]
|
|
# FRANCE A IRI - [MAO] VIA
|
|
# FRANCE A IRO R [MAO]
|
|
elif order_split.destination:
|
|
buffer.append(parse_string(Province, order_split.destination))
|
|
|
|
# FRANCE A IRI - MAO [VIA]
|
|
if order_split.via_flag:
|
|
buffer.append(parse_string(OrderType, order_split.via_flag))
|
|
|
|
return b''.join([bytes(clause) for clause in buffer])
|
|
|
|
class Order(AbstractClause):
|
|
""" Each clause is an order
|
|
Syntax: ((power unit_type location) order_type province)
|
|
"""
|
|
def __init__(self):
|
|
""" Constructor """
|
|
super(Order, self).__init__()
|
|
self._bytes = b''
|
|
self._str = ''
|
|
self._power_name = None
|
|
|
|
@property
|
|
def power_name(self):
|
|
""" The power name """
|
|
return self._power_name
|
|
|
|
def __bytes__(self):
|
|
""" Define the DAIDE bytes representation """
|
|
return self._bytes
|
|
|
|
def __str__(self):
|
|
""" Return the Diplomacy str """
|
|
return self._str
|
|
|
|
def from_bytes(self, daide_bytes, on_error='raise'):
|
|
""" Builds the clause from a byte array
|
|
|
|
:param daide_bytes: The bytes to use to build the clause
|
|
:param on_error: The action to take when an error is encountered ('raise', 'warn', 'ignore')
|
|
:return: The remaining (unparsed) bytes
|
|
"""
|
|
order_group_bytes, remaining_bytes = break_next_group(daide_bytes)
|
|
|
|
# Can't find the order
|
|
if not order_group_bytes:
|
|
self.error(on_error, 'Unable to find a set of parentheses to extract the order clause.')
|
|
return daide_bytes
|
|
|
|
# Extract its content
|
|
self._bytes = order_group_bytes
|
|
|
|
# Parsing the unit group (or just the power)
|
|
order_group_bytes = strip_parentheses(order_group_bytes)
|
|
unit, order_group_bytes = parse_bytes(Unit, order_group_bytes, on_error='ignore')
|
|
power = None
|
|
if not unit:
|
|
power, order_group_bytes = parse_bytes(Power, order_group_bytes, on_error='ignore')
|
|
|
|
order_type, order_group_bytes = parse_bytes(OrderType, order_group_bytes, on_error=on_error)
|
|
order_type_str = str(order_type)
|
|
|
|
if order_type_str == 'WAIVE':
|
|
str_buffer = [order_type_str]
|
|
|
|
elif order_type_str:
|
|
# Hold, Disband
|
|
str_buffer = [str(unit), order_type_str]
|
|
|
|
# Move order
|
|
if order_type_str == '-':
|
|
province, order_group_bytes = parse_bytes(Province, order_group_bytes, on_error=on_error)
|
|
str_buffer += [str(province)]
|
|
second_order_type, order_group_bytes = parse_bytes(OrderType, order_group_bytes, on_error='ignore')
|
|
if str(second_order_type) == 'VIA':
|
|
str_buffer += [str(second_order_type)]
|
|
province_list, order_group_bytes = break_next_group(order_group_bytes)
|
|
del province_list
|
|
|
|
# Support
|
|
elif order_type_str == 'S':
|
|
other_unit, order_group_bytes = parse_bytes(Unit, order_group_bytes, on_error=on_error)
|
|
str_buffer += [str(other_unit)]
|
|
second_order_type, order_group_bytes = parse_bytes(OrderType, order_group_bytes, on_error='ignore')
|
|
if str(second_order_type) == '-':
|
|
province, order_group_bytes = parse_bytes(Province, order_group_bytes, on_error=on_error)
|
|
str_buffer += [str(second_order_type), str(province)]
|
|
|
|
# Convoy
|
|
elif order_type_str == 'C':
|
|
other_unit, order_group_bytes = parse_bytes(Unit, order_group_bytes, on_error=on_error)
|
|
second_order_type, order_group_bytes = parse_bytes(OrderType, order_group_bytes, on_error=on_error)
|
|
province, order_group_bytes = parse_bytes(Province, order_group_bytes, on_error=on_error)
|
|
str_buffer += [str(other_unit), str(second_order_type), str(province)]
|
|
|
|
# Retreat
|
|
elif order_type_str == 'R':
|
|
province, order_group_bytes = parse_bytes(Province, order_group_bytes, on_error=on_error)
|
|
str_buffer += [str(province)]
|
|
|
|
else:
|
|
self.error(on_error, 'Unable to find a unit, a power or an order to build the order clause')
|
|
return daide_bytes
|
|
|
|
if order_group_bytes:
|
|
self.error(on_error, '{} bytes remaining. Order is malformed'.format(len(order_group_bytes)))
|
|
return daide_bytes
|
|
|
|
self._power_name = str(power) if power else unit.power_name
|
|
self._str = ' '.join(str_buffer)
|
|
return remaining_bytes
|
|
|
|
def from_string(self, string, on_error='raise'):
|
|
""" Builds the clause from a string
|
|
|
|
:param string: The string to use to build the clause
|
|
:param on_error: The action to take when an error is encountered ('raise', 'warn', 'ignore')
|
|
"""
|
|
raise NotImplementedError()
|