aboutsummaryrefslogtreecommitdiff
path: root/diplomacy/engine/message.py
blob: 8094bfeb78ef95d73a12d6836a4e023548413e0f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# ==============================================================================
# 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/>.
# ==============================================================================
""" Game message. Represent a message exchanged inside a game.

    Possible messages exchanges:

    - power 1 -> power 2
    - power -> all game
    - system -> power
    - system -> all game
    - system -> observers
    - system -> omniscient observers

    Sender `system` is identified with constant SYSTEM defined below.

    Recipients `all game`, `observers` and `omniscient observers` are identified respectively with
    constants GLOBAL, OBSERVER and OMNISCIENT defined below.

    Consider using Game methods to generate appropriate messages instead of this class directly:

    - Game.new_power_message() to send a message from a power to another.
    - Game.new_global_message() to send a message from a power to all game.
    - ServerGame.new_system_message() to send a server system message.
      Use constant names defined below to specify recipient for system message when it's not a power name
      (GLOBAL, OBSERVER or OMNISCIENT).
"""
from diplomacy.utils import parsing, strings
from diplomacy.utils.jsonable import Jsonable

SYSTEM = 'SYSTEM'  # sender
GLOBAL = 'GLOBAL'  # recipient (all powers)
OBSERVER = 'OBSERVER'  # recipient (all observer tokens)
OMNISCIENT = 'OMNISCIENT'  # recipient (all omniscient tokens)

class Message(Jsonable):
    """ Message class.

        Properties:

        - **sender**: message sender name: either SYSTEM or a power name.
        - **recipient**: message recipient name: either GLOBAL, OBSERVER, OMNISCIENT or a power name.
        - **time_sent**: message timestamp in microseconds.
        - **phase**: short name of game phase when message is sent.
        - **message**: message body.

        **Note about timestamp management**:

        We assume a message has an unique timestamp inside one game. To respect this rule, the server is the only one
        responsible for generating message timestamps. This allow to generate timestamp or only 1 same machine (server)
        instead of managing timestamps from many user machines, to prevent timestamp inconsistency when messages
        are stored on server. Therefore, message timestamp is the time when server stores the message, not the time
        when message was sent by any client.
    """
    __slots__ = ['sender', 'recipient', 'time_sent', 'phase', 'message']
    model = {
        strings.SENDER: str,                                # either SYSTEM or a power name.
        strings.RECIPIENT: str,                             # either GLOBAL, OBSERVER, OMNISCIENT or a power name.
        strings.TIME_SENT: parsing.OptionalValueType(int),  # given by server.
        strings.PHASE: str,                                 # phase short name (e.g. 'S1901M' or 'COMPLETED')
        strings.MESSAGE: str,
    }

    def __init__(self, **kwargs):
        self.sender = None                  # type: str
        self.recipient = None               # type: str
        self.time_sent = None               # type: int
        self.phase = None                   # type: str
        self.message = None                 # type: str
        super(Message, self).__init__(**kwargs)

    def __str__(self):
        return '[%d/%s/%s->%s](%s)' % (self.time_sent or 0, self.phase, self.sender, self.recipient, self.message)

    def __hash__(self):
        return hash(self.time_sent)

    def __eq__(self, other):
        assert isinstance(other, Message)
        return self.time_sent == other.time_sent

    def __ne__(self, other):
        assert isinstance(other, Message)
        return self.time_sent != other.time_sent

    def __lt__(self, other):
        assert isinstance(other, Message)
        return self.time_sent < other.time_sent

    def __gt__(self, other):
        assert isinstance(other, Message)
        return self.time_sent > other.time_sent

    def __le__(self, other):
        assert isinstance(other, Message)
        return self.time_sent <= other.time_sent

    def __ge__(self, other):
        assert isinstance(other, Message)
        return self.time_sent >= other.time_sent

    def is_global(self):
        """ Return True if this message is global. """
        return self.recipient == GLOBAL

    def for_observer(self):
        """ Return True if this message is sent to observers. """
        return self.recipient == OBSERVER