1 | from __future__ import annotations
|
---|
2 |
|
---|
3 | from typing import List, Dict, Optional, TYPE_CHECKING
|
---|
4 |
|
---|
5 | from geniusweb.actions.Accept import Accept
|
---|
6 | from geniusweb.actions.Action import Action
|
---|
7 | from geniusweb.actions.EndNegotiation import EndNegotiation
|
---|
8 | from geniusweb.actions.Offer import Offer
|
---|
9 | from geniusweb.actions.PartyId import PartyId
|
---|
10 | from geniusweb.inform.Agreements import Agreements
|
---|
11 | from geniusweb.issuevalue.Bid import Bid
|
---|
12 | from geniusweb.progress.Progress import Progress
|
---|
13 | from geniusweb.progress.ProgressRounds import ProgressRounds
|
---|
14 | from geniusweb.protocol.ProtocolException import ProtocolException
|
---|
15 | from geniusweb.protocol.session.DefaultSessionState import DefaultSessionState
|
---|
16 | from geniusweb.protocol.session.SessionResult import SessionResult
|
---|
17 | from geniusweb.protocol.session.saop.SAOPSettings import SAOPSettings
|
---|
18 | from geniusweb.references.PartyWithProfile import PartyWithProfile
|
---|
19 |
|
---|
20 |
|
---|
21 | class SAOPState (DefaultSessionState[ "SAOPState", "SAOPSettings"]):
|
---|
22 | '''
|
---|
23 | Immutable.
|
---|
24 | '''
|
---|
25 |
|
---|
26 | def __init__(self, actions:List[Action] , connections:List[PartyId] ,
|
---|
27 | progress:Optional[Progress] , settings: SAOPSettings ,
|
---|
28 | partyprofiles:Dict[PartyId, PartyWithProfile]={},
|
---|
29 | error:Optional[ProtocolException]=None):
|
---|
30 | '''
|
---|
31 |
|
---|
32 | @param actions the actions done by the parties
|
---|
33 | @param conns the existing party connections. If null, default
|
---|
34 | empty list is used. Can be less than 2 parties in
|
---|
35 | the first phases of the setup or after parties
|
---|
36 | disconnected.
|
---|
37 | @param progress the {@link Progress} line. can be null
|
---|
38 | @param settings the {@link SAOPSettings}
|
---|
39 | @param partyprofiles map with the {@link PartyWithProfile} for connected
|
---|
40 | parties. null is equivalent to an empty map.
|
---|
41 | @param e the {@link ProtocolException}, or null if none
|
---|
42 | occurred.
|
---|
43 | '''
|
---|
44 | super().__init__(actions, connections, progress, settings, partyprofiles, error)
|
---|
45 |
|
---|
46 | def With(self, actions:List[Action] , conns: List[PartyId],
|
---|
47 | progr:Optional[Progress] , settings: SAOPSettings,
|
---|
48 | partyprofiles:Dict[PartyId, PartyWithProfile] , e:Optional[ProtocolException]) -> "SAOPState":
|
---|
49 | assert isinstance(actions, list)
|
---|
50 | # assert isinstance(settings, SAOPSettings)
|
---|
51 | assert isinstance(partyprofiles, dict)
|
---|
52 | return SAOPState(actions, conns, progr, settings, partyprofiles, e);
|
---|
53 |
|
---|
54 | def getAgreements(self) -> Agreements:
|
---|
55 | agree = Agreements()
|
---|
56 | acts = self.getActions()
|
---|
57 | nparticipants = len(self.getConnections())
|
---|
58 | if nparticipants < 2 or len(acts) < nparticipants:
|
---|
59 | return agree;
|
---|
60 | offer = acts[len(acts) - nparticipants]
|
---|
61 | if not isinstance(offer, Offer):
|
---|
62 | return agree;
|
---|
63 | bid = offer.getBid()
|
---|
64 |
|
---|
65 | # check that the last n-1 are accepts.
|
---|
66 | allaccept = all([ (isinstance(act, Accept) and bid == act.getBid())\
|
---|
67 | for act in acts[-(nparticipants - 1):]])
|
---|
68 | if allaccept:
|
---|
69 | agree = Agreements({party: bid for party in self._getParties()})
|
---|
70 | return agree
|
---|
71 |
|
---|
72 | def _getParties(self) -> List[PartyId]:
|
---|
73 | '''
|
---|
74 | @return all currently connected parties.
|
---|
75 | '''
|
---|
76 | return self.getConnections()
|
---|
77 |
|
---|
78 | def isFinal(self, currentTimeMs:int) -> bool:
|
---|
79 | acts = self.getActions()
|
---|
80 | return super().isFinal(currentTimeMs) \
|
---|
81 | or self.getAgreements().getMap() != {} \
|
---|
82 | or (not acts == [] and isinstance(acts[-1], EndNegotiation))
|
---|
83 |
|
---|
84 | def WithAction(self, actor:PartyId , action:Action) -> "SAOPState":
|
---|
85 | return super().WithAction(actor, action).WithProgress(self.advanceProgress())
|
---|
86 |
|
---|
87 | def checkAction(self, actor:PartyId, action:Action) -> Optional[str]:
|
---|
88 | msg = super().checkAction(actor, action)
|
---|
89 | if msg:
|
---|
90 | return msg
|
---|
91 |
|
---|
92 | if actor != self._getNextActor():
|
---|
93 | return "Party does not have the turn "
|
---|
94 |
|
---|
95 | # check protocol is followed for specific actions
|
---|
96 | if isinstance(action, Accept):
|
---|
97 | bid = self._getLastBid()
|
---|
98 | if not bid:
|
---|
99 | return "Accept without a recent offer";
|
---|
100 |
|
---|
101 | if bid != action.getBid():
|
---|
102 | return "Party accepts a bid differing from the last offer ="\
|
---|
103 | +str(bid) + ", action=" + str(action) + ")"
|
---|
104 | return None
|
---|
105 | elif isinstance(action, Offer):
|
---|
106 | return None
|
---|
107 | elif isinstance(action, EndNegotiation):
|
---|
108 | return None
|
---|
109 | return "Action " + str(action) + " is not allowed in SAOP"
|
---|
110 |
|
---|
111 | def _getLastBid(self) -> Optional[ Bid ]:
|
---|
112 | '''
|
---|
113 | Check up to nparticipants-1 steps back if there was an offer.
|
---|
114 |
|
---|
115 | @return Bid from the most recent offer, or null if no such offer
|
---|
116 | '''
|
---|
117 | nparticipants = len(self.getConnections())
|
---|
118 | acts = self.getActions()
|
---|
119 | n = len(acts) - 1
|
---|
120 | while n > len(acts) - nparticipants and n >= 0:
|
---|
121 | action = acts[n]
|
---|
122 | if isinstance(action , Offer):
|
---|
123 | return action.getBid()
|
---|
124 | n = n - 1
|
---|
125 | return None
|
---|
126 |
|
---|
127 | def _getNextActor(self) -> PartyId:
|
---|
128 | '''
|
---|
129 | @return the next actor in the current state. Assumes 1 action per actor
|
---|
130 | every time. NOTE if party disconnects this would jump wildly but
|
---|
131 | nego stops then anyway
|
---|
132 | '''
|
---|
133 | return self.getConnections()[len(self.getActions()) % len(self.getConnections())]
|
---|
134 |
|
---|
135 | def advanceProgress(self) -> Progress:
|
---|
136 | '''
|
---|
137 | @return new progress state
|
---|
138 | '''
|
---|
139 | newprogress = self.getProgress()
|
---|
140 | assert newprogress
|
---|
141 | if isinstance(newprogress, ProgressRounds) and self._isLastActor():
|
---|
142 | newprogress = newprogress.advance()
|
---|
143 | return newprogress
|
---|
144 |
|
---|
145 | def _isLastActor(self) -> bool:
|
---|
146 | '''
|
---|
147 | @return true if the current actor is the last actor in the list
|
---|
148 | '''
|
---|
149 | nparticipants = len(self.getConnections())
|
---|
150 | return len(self.getActions()) % nparticipants == nparticipants - 1;
|
---|
151 |
|
---|
152 | def getResults(self) -> List[SessionResult]:
|
---|
153 | return [SessionResult(self.getPartyProfiles(), self.getAgreements(),
|
---|
154 | {}, self.getError())]
|
---|