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