[88] | 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 | class 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())
|
---|