1import re 2 3from gi.repository import GObject 4 5from pychess.Utils.const import DRAW_OFFER, ABORT_OFFER, ADJOURN_OFFER, TAKEBACK_OFFER, \ 6 PAUSE_OFFER, RESUME_OFFER, SWITCH_OFFER, RESIGNATION, FLAG_CALL, MATCH_OFFER, \ 7 WHITE, ACTION_ERROR_SWITCH_UNDERWAY, ACTION_ERROR_CLOCK_NOT_STARTED, \ 8 ACTION_ERROR_CLOCK_NOT_PAUSED, ACTION_ERROR_NONE_TO_ACCEPT, ACTION_ERROR_NONE_TO_WITHDRAW, \ 9 ACTION_ERROR_NONE_TO_DECLINE, ACTION_ERROR_TOO_LARGE_UNDO, ACTION_ERROR_NOT_OUT_OF_TIME 10 11from pychess.Utils.Offer import Offer 12from pychess.System.Log import log 13from pychess.ic import GAME_TYPES, VariantGameType 14from pychess.ic.FICSObjects import FICSChallenge 15 16names = r"\w+(?:\([A-Z\*]+\))*" 17 18rated = "(rated|unrated)" 19colors = r"(?:\[(white|black)\])?" 20ratings = r"\(([0-9\ \-\+]{1,4}[E P]?)\)" 21loaded_from = r"(?: Loaded from (wild[/\w]*))?" 22adjourned = r"(?: (\(adjourned\)))?" 23 24matchreUntimed = re.compile(r"(\w+) %s %s ?(\w+) %s %s (untimed)\s*" % 25 (ratings, colors, ratings, rated)) 26matchre = re.compile( 27 r"(\w+) %s %s ?(\w+) %s %s (\w+) (\d+) (\d+)%s%s" % 28 (ratings, colors, ratings, rated, loaded_from, adjourned)) 29 30# <pf> 39 w=GuestDVXV t=match p=GuestDVXV (----) [black] GuestNXMP (----) unrated blitz 2 12 31# <pf> 16 w=GuestDVXV t=match p=GuestDVXV (----) GuestNXMP (----) unrated wild 2 12 Loaded from wild/fr 32# <pf> 39 w=GuestDVXV t=match p=GuestDVXV (----) GuestNXMP (----) unrated blitz 2 12 (adjourned) 33# <pf> 45 w=GuestGYXR t=match p=GuestGYXR (----) Lobais (----) unrated losers 2 12 34# <pf> 45 w=GuestYDDR t=match p=GuestYDDR (----) mgatto (1358) unrated untimed 35# <pf> 71 w=joseph t=match p=joseph (1632) mgatto (1742) rated wild 5 1 Loaded from wild/fr (adjourned) 36# <pf> 59 w=antiseptic t=match p=antiseptic (1945) mgatto (1729) rated wild 6 1 Loaded from wild/4 (adjourned) 37# 38# Known offers: abort accept adjourn draw match pause unpause switch takeback 39# 40 41strToOfferType = { 42 "draw": DRAW_OFFER, 43 "abort": ABORT_OFFER, 44 "adjourn": ADJOURN_OFFER, 45 "takeback": TAKEBACK_OFFER, 46 "pause": PAUSE_OFFER, 47 "unpause": RESUME_OFFER, 48 "switch": SWITCH_OFFER, 49 "resign": RESIGNATION, 50 "flag": FLAG_CALL, 51 "match": MATCH_OFFER 52} 53 54offerTypeToStr = {} 55for k, v in strToOfferType.items(): 56 offerTypeToStr[v] = k 57 58 59class OfferManager(GObject.GObject): 60 61 __gsignals__ = { 62 'onOfferAdd': (GObject.SignalFlags.RUN_FIRST, None, (object, )), 63 'onOfferRemove': (GObject.SignalFlags.RUN_FIRST, None, (object, )), 64 'onOfferDeclined': (GObject.SignalFlags.RUN_FIRST, None, (object, )), 65 'onChallengeAdd': (GObject.SignalFlags.RUN_FIRST, None, (object, )), 66 'onChallengeRemove': (GObject.SignalFlags.RUN_FIRST, None, (int, )), 67 'onActionError': (GObject.SignalFlags.RUN_FIRST, None, (object, int)), 68 } 69 70 def __init__(self, connection): 71 GObject.GObject.__init__(self) 72 73 self.connection = connection 74 75 self.connection.expect_line( 76 self.onOfferAdd, r"<p(t|f)> (\d+) w=%s t=(\w+) p=(.+)" % names) 77 self.connection.expect_line(self.onOfferRemove, r"<pr> (\d+)") 78 79 for ficsstring, offer, error in ( 80 ("You cannot switch sides once a game is underway.", 81 Offer(SWITCH_OFFER), ACTION_ERROR_SWITCH_UNDERWAY), 82 ("Opponent is not out of time.", Offer(FLAG_CALL), 83 ACTION_ERROR_NOT_OUT_OF_TIME), ("The clock is not ticking yet.", 84 Offer(PAUSE_OFFER), 85 ACTION_ERROR_CLOCK_NOT_STARTED), 86 ("The clock is not ticking.", Offer(FLAG_CALL), 87 ACTION_ERROR_CLOCK_NOT_STARTED), ("The clock is not paused.", 88 Offer(RESUME_OFFER), 89 ACTION_ERROR_CLOCK_NOT_PAUSED)): 90 self.connection.expect_line( 91 lambda match: self.emit("onActionError", offer, error), 92 ficsstring) 93 94 self.connection.expect_line( 95 self.notEnoughMovesToUndo, 96 r"There are (?:(no)|only (\d+) half) moves in your game\.") 97 98 self.connection.expect_line(self.noOffersToAccept, 99 "There are no ([^ ]+) offers to (accept).") 100 101 self.connection.expect_line( 102 self.onOfferDeclined, 103 r"\w+ declines the (draw|takeback|pause|unpause|abort|adjourn) request\.") 104 105 self.lastPly = 0 106 self.offers = {} 107 108 self.connection.client.run_command("iset pendinfo 1") 109 110 def onOfferDeclined(self, match): 111 log.debug("OfferManager.onOfferDeclined: match.string=%s" % 112 match.string) 113 type = match.groups()[0] 114 offer = Offer(strToOfferType[type]) 115 self.emit("onOfferDeclined", offer) 116 117 def noOffersToAccept(self, match): 118 offertype, request = match.groups() 119 if request == "accept": 120 error = ACTION_ERROR_NONE_TO_ACCEPT 121 elif request == "withdraw": 122 error = ACTION_ERROR_NONE_TO_WITHDRAW 123 elif request == "decline": 124 error = ACTION_ERROR_NONE_TO_DECLINE 125 offer = Offer(strToOfferType[offertype]) 126 self.emit("onActionError", offer, error) 127 128 def notEnoughMovesToUndo(self, match): 129 ply = match.groups()[0] or match.groups()[1] 130 if ply == "no": 131 ply = 0 132 else: 133 ply = int(ply) 134 offer = Offer(TAKEBACK_OFFER, param=ply) 135 self.emit("onActionError", offer, ACTION_ERROR_TOO_LARGE_UNDO) 136 137 def onOfferAdd(self, match): 138 log.debug("OfferManager.onOfferAdd: match.string=%s" % match.string) 139 140 tofrom, index, offertype, parameters = match.groups() 141 index = int(index) 142 143 if tofrom == "t": 144 # ICGameModel keeps track of the offers we've sent ourselves, so we 145 # don't need this 146 return 147 if offertype not in strToOfferType: 148 log.warning("OfferManager.onOfferAdd: Declining unknown offer type: " + 149 "offertype=%s parameters=%s index=%d" % (offertype, parameters, index)) 150 self.connection.client.run_command("decline %d" % index) 151 return 152 offertype = strToOfferType[offertype] 153 if offertype == TAKEBACK_OFFER: 154 offer = Offer(offertype, param=int(parameters), index=index) 155 else: 156 offer = Offer(offertype, index=index) 157 self.offers[offer.index] = offer 158 159 if offer.type == MATCH_OFFER: 160 is_adjourned = False 161 if matchreUntimed.match(parameters) is not None: 162 fname, frating, col, tname, trating, rated, type = \ 163 matchreUntimed.match(parameters).groups() 164 mins = 0 165 incr = 0 166 gametype = GAME_TYPES["untimed"] 167 else: 168 fname, frating, col, tname, trating, rated, gametype, mins, \ 169 incr, wildtype, adjourned = matchre.match(parameters).groups() 170 if (wildtype and "adjourned" in wildtype) or \ 171 (adjourned and "adjourned" in adjourned): 172 is_adjourned = True 173 if wildtype and "wild" in wildtype: 174 gametype = wildtype 175 176 try: 177 gametype = GAME_TYPES[gametype] 178 except KeyError: 179 log.warning("OfferManager.onOfferAdd: auto-declining " + 180 "unknown offer type: '%s'\n" % gametype) 181 self.decline(offer) 182 del self.offers[offer.index] 183 return 184 185 player = self.connection.players.get(fname) 186 rating = frating.strip() 187 rating = int(rating) if rating.isdigit() else 0 188 if player.ratings[gametype.rating_type] != rating: 189 player.ratings[gametype.rating_type] = rating 190 player.emit("ratings_changed", gametype.rating_type, player) 191 rated = rated != "unrated" 192 challenge = FICSChallenge(index, 193 player, 194 int(mins), 195 int(incr), 196 rated, 197 col, 198 gametype, 199 adjourned=is_adjourned) 200 self.emit("onChallengeAdd", challenge) 201 202 else: 203 log.debug("OfferManager.onOfferAdd: emitting onOfferAdd: %s" % 204 offer) 205 self.emit("onOfferAdd", offer) 206 207 def onOfferRemove(self, match): 208 log.debug("OfferManager.onOfferRemove: match.string=%s" % match.string) 209 index = int(match.groups()[0]) 210 if index not in self.offers: 211 return 212 if self.offers[index].type == MATCH_OFFER: 213 self.emit("onChallengeRemove", index) 214 else: 215 self.emit("onOfferRemove", self.offers[index]) 216 del self.offers[index] 217 218 ### 219 220 def challenge(self, 221 player_name, 222 game_type, 223 startmin, 224 incsec, 225 rated, 226 color=None): 227 log.debug("OfferManager.challenge: %s %s %s %s %s %s" % 228 (player_name, game_type, startmin, incsec, rated, color)) 229 rchar = rated and "r" or "u" 230 if color is not None: 231 cchar = color == WHITE and "w" or "b" 232 else: 233 cchar = "" 234 s = "match %s %d %d %s %s" % \ 235 (player_name, startmin, incsec, rchar, cchar) 236 if isinstance(game_type, VariantGameType): 237 s += " " + game_type.seek_text 238 self.connection.client.run_command(s) 239 240 def offer(self, offer): 241 log.debug("OfferManager.offer: %s" % offer) 242 s = offerTypeToStr[offer.type] 243 if offer.type == TAKEBACK_OFFER: 244 s += " " + str(offer.param) 245 self.connection.client.run_command(s) 246 247 ### 248 249 def withdraw(self, offer): 250 log.debug("OfferManager.withdraw: %s" % offer) 251 self.connection.client.run_command("withdraw t %s" % 252 offerTypeToStr[offer.type]) 253 254 def accept(self, offer): 255 log.debug("OfferManager.accept: %s" % offer) 256 if offer.index is not None: 257 self.acceptIndex(offer.index) 258 else: 259 self.connection.client.run_command("accept t %s" % 260 offerTypeToStr[offer.type]) 261 262 def decline(self, offer): 263 log.debug("OfferManager.decline: %s" % offer) 264 if offer.index is not None: 265 self.declineIndex(offer.index) 266 else: 267 self.connection.client.run_command("decline t %s" % 268 offerTypeToStr[offer.type]) 269 270 def acceptIndex(self, index): 271 log.debug("OfferManager.acceptIndex: index=%s" % index) 272 self.connection.client.run_command("accept %s" % index) 273 274 def declineIndex(self, index): 275 log.debug("OfferManager.declineIndex: index=%s" % index) 276 self.connection.client.run_command("decline %s" % index) 277 278 def playIndex(self, index): 279 log.debug("OfferManager.playIndex: index=%s" % index) 280 self.connection.client.run_command("play %s" % index) 281