source: geniuswebcore/geniusweb/protocol/session/saop/SAOPState.py@ 92

Last change on this file since 92 was 90, checked in by Bart Vastenhouw, 3 years ago

Refactor to help reusing partiesserver.

File size: 5.4 KB
Line 
1from __future__ import annotations
2
3from typing import List, Dict, Optional, TYPE_CHECKING
4
5from geniusweb.actions.Accept import Accept
6from geniusweb.actions.Action import Action
7from geniusweb.actions.EndNegotiation import EndNegotiation
8from geniusweb.actions.Offer import Offer
9from geniusweb.actions.PartyId import PartyId
10from geniusweb.inform.Agreements import Agreements
11from geniusweb.issuevalue.Bid import Bid
12from geniusweb.progress.Progress import Progress
13from geniusweb.progress.ProgressRounds import ProgressRounds
14from geniusweb.protocol.ProtocolException import ProtocolException
15from geniusweb.protocol.session.DefaultSessionState import DefaultSessionState
16from geniusweb.protocol.session.SessionResult import SessionResult
17from geniusweb.protocol.session.saop.SAOPSettings import SAOPSettings
18from geniusweb.references.PartyWithProfile import PartyWithProfile
19
20class SAOPState (DefaultSessionState[ "SAOPState","SAOPSettings"]):
21 '''
22 Immutable.
23 '''
24
25 def __init__(self, actions:List[Action] , connections:List[PartyId] ,
26 progress:Optional[Progress] , settings: SAOPSettings ,
27 partyprofiles:Dict[PartyId, PartyWithProfile]={},
28 error:Optional[ProtocolException]=None) :
29 '''
30
31 @param actions the actions done by the parties
32 @param conns the existing party connections. If null, default
33 empty list is used. Can be less than 2 parties in
34 the first phases of the setup or after parties
35 disconnected.
36 @param progress the {@link Progress} line. can be null
37 @param settings the {@link SAOPSettings}
38 @param partyprofiles map with the {@link PartyWithProfile} for connected
39 parties. null is equivalent to an empty map.
40 @param e the {@link ProtocolException}, or null if none
41 occurred.
42 '''
43 super().__init__(actions, connections, progress, settings, partyprofiles, error)
44
45 def With(self, actions:List[Action] , conns: List[PartyId],
46 progr:Optional[Progress] , settings: SAOPSettings,
47 partyprofiles:Dict[PartyId, PartyWithProfile] , e:Optional[ProtocolException]) -> "SAOPState" :
48 assert isinstance(actions, list)
49 #assert isinstance(settings, SAOPSettings)
50 assert isinstance(partyprofiles, dict)
51 return SAOPState(actions, conns, progr, settings, partyprofiles, e);
52
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
112 def _getLastBid(self)-> Optional[ Bid ]:
113 '''
114 Check up to nparticipants-1 steps back if there was an offer.
115
116 @return Bid from the most recent offer, or null if no such offer
117 '''
118 nparticipants = len(self.getConnections())
119 acts = self.getActions()
120 n = len(acts) - 1
121 while n > len(acts) - nparticipants and n >= 0:
122 action = acts[n]
123 if isinstance(action ,Offer):
124 return action.getBid()
125 n=n-1
126 return None
127
128 def _getNextActor(self)->PartyId :
129 '''
130 @return the next actor in the current state. Assumes 1 action per actor
131 every time. NOTE if party disconnects this would jump wildly but
132 nego stops then anyway
133 '''
134 return self.getConnections()[len(self.getActions()) % len(self.getConnections())]
135
136 def advanceProgress(self)-> Progress :
137 '''
138 @return new progress state
139 '''
140 newprogress = self.getProgress()
141 assert newprogress
142 if isinstance(newprogress,ProgressRounds) and self._isLastActor():
143 newprogress = newprogress.advance()
144 return newprogress
145
146 def _isLastActor(self) -> bool:
147 '''
148 @return true if the current actor is the last actor in the list
149 '''
150 nparticipants = len(self.getConnections())
151 return len(self.getActions()) % nparticipants == nparticipants - 1;
152
153 def getResult(self) -> SessionResult:
154 return SessionResult(self.getPartyProfiles(), self.getAgreements(),
155 {}, self.getError())
Note: See TracBrowser for help on using the repository browser.