1import asyncio 2from collections import defaultdict 3 4from pychess.Players.Player import Player, PlayerIsDead, PassInterrupt, TurnInterrupt, GameEnded 5from pychess.Utils.Move import parseSAN, toAN 6from pychess.Utils.lutils.lmove import ParsingError 7from pychess.Utils.Offer import Offer 8from pychess.Utils.const import REMOTE, UNFINISHED_STATES, CHAT_ACTION, CASTLE_KK, \ 9 FISCHERRANDOMCHESS, CASTLE_SAN, TAKEBACK_OFFER 10from pychess.System.Log import log 11 12 13class ICPlayer(Player): 14 __type__ = REMOTE 15 16 def __init__(self, 17 gamemodel, 18 ichandle, 19 gameno, 20 color, 21 name, 22 icrating=None): 23 Player.__init__(self) 24 self.offers = {} 25 self.setName(name) 26 self.ichandle = ichandle 27 self.icrating = icrating 28 self.color = color 29 self.gameno = gameno 30 self.gamemodel = gamemodel 31 self.pass_interrupt = False 32 self.turn_interrupt = False 33 34 self.connection = connection = self.gamemodel.connection 35 36 self.connections = connections = defaultdict(list) 37 connections[connection.om].append(connection.om.connect( 38 "onOfferAdd", self.__onOfferAdd)) 39 connections[connection.om].append(connection.om.connect( 40 "onOfferRemove", self.__onOfferRemove)) 41 connections[connection.om].append(connection.om.connect( 42 "onOfferDeclined", self.__onOfferDeclined)) 43 connections[connection.cm].append(connection.cm.connect( 44 "privateMessage", self.__onPrivateMessage)) 45 46 self.cid = self.gamemodel.connect_after("game_terminated", self.on_game_terminated) 47 48 def on_game_terminated(self, model): 49 self.gamemodel.disconnect(self.cid) 50 51 def getICHandle(self): 52 return self.name 53 54 @property 55 def time(self): 56 return self.gamemodel.timemodel.getPlayerTime(self.color) 57 58 @property 59 def move_queue(self): 60 return self.gamemodel.ficsgame.move_queue 61 62 # Handle signals from the connection 63 64 def __onOfferAdd(self, om, offer): 65 if self.gamemodel.status in UNFINISHED_STATES and not self.gamemodel.isObservationGame( 66 ): 67 log.debug("ICPlayer.__onOfferAdd: emitting offer: self.gameno=%s self.name=%s %s" % ( 68 self.gameno, self.name, offer)) 69 self.offers[offer.index] = offer 70 self.emit("offer", offer) 71 72 def __onOfferDeclined(self, om, offer): 73 for offer_ in list(self.gamemodel.offers.keys()): 74 if offer.type == offer_.type: 75 offer.param = offer_.param 76 log.debug("ICPlayer.__onOfferDeclined: emitting decline for %s" % 77 offer) 78 self.emit("decline", offer) 79 80 def __onOfferRemove(self, om, offer): 81 if offer.index in self.offers: 82 log.debug("ICPlayer.__onOfferRemove: emitting withdraw: \ 83 self.gameno=%s self.name=%s %s" % (self.gameno, self.name, offer)) 84 # self.emit("withdraw", self.offers[offer.index]) 85 del self.offers[offer.index] 86 87 def __onPrivateMessage(self, cm, name, title, isadmin, text): 88 if name == self.ichandle: 89 self.emit("offer", Offer(CHAT_ACTION, param=text)) 90 91 def __disconnect(self): 92 log.debug("ICPlayer.__disconnect: %s" % self.name) 93 if self.connections is None: 94 return 95 for obj in self.connections: 96 for handler_id in self.connections[obj]: 97 if obj.handler_is_connected(handler_id): 98 obj.disconnect(handler_id) 99 self.connections = None 100 101 def end(self, status=None, reason=None): 102 log.debug("ICPlayer.end: %s" % self.name) 103 self.__disconnect() 104 self.move_queue.put_nowait("end") 105 106 def kill(self, reason): 107 self.__disconnect() 108 self.move_queue.put_nowait("del") 109 110 # Send the player move updates 111 112 @asyncio.coroutine 113 def makeMove(self, board1, move, board2): 114 log.debug("ICPlayer.makemove: id(self)=%d self=%s move=%s board1=%s board2=%s" % ( 115 id(self), self, move, board1, board2)) 116 if board2 and not self.gamemodel.isObservationGame(): 117 # TODO: Will this work if we just always use CASTLE_SAN? 118 castle_notation = CASTLE_KK 119 if board2.variant == FISCHERRANDOMCHESS: 120 castle_notation = CASTLE_SAN 121 self.connection.bm.sendMove(toAN(board2, move, castleNotation=castle_notation)) 122 # wait for fics to send back our move we made 123 item = yield from self.move_queue.get() 124 log.debug("ICPlayer.makeMove: fics sent back the move we made") 125 126 item = yield from self.move_queue.get() 127 try: 128 if item == "end": 129 log.debug("ICPlayer.makeMove got: end") 130 raise GameEnded 131 elif item == "del": 132 log.debug("ICPlayer.makeMove got: del") 133 raise PlayerIsDead 134 elif item == "stm": 135 log.debug("ICPlayer.makeMove got: stm") 136 self.turn_interrupt = False 137 raise TurnInterrupt 138 elif item == "fen": 139 log.debug("ICPlayer.makeMove got: fen") 140 self.turn_interrupt = False 141 raise TurnInterrupt 142 elif item == "pass": 143 log.debug("ICPlayer.makeMove got: pass") 144 self.pass_interrupt = False 145 raise PassInterrupt 146 147 gameno, ply, curcol, lastmove, fen, wname, bname, wms, bms = item 148 log.debug("ICPlayer.makeMove got: %s %s %s %s" % (gameno, ply, curcol, lastmove)) 149 self.gamemodel.update_board(gameno, ply, curcol, lastmove, fen, wname, bname, wms, bms) 150 151 if self.turn_interrupt: 152 self.turn_interrupt = False 153 raise TurnInterrupt 154 155 if self.pass_interrupt: 156 self.pass_interrupt = False 157 raise PassInterrupt 158 159 if ply < board1.ply: 160 # This should only happen in an observed game 161 board1 = self.gamemodel.getBoardAtPly(max(ply - 1, 0)) 162 log.debug("ICPlayer.makemove: id(self)=%d self=%s from queue got: ply=%d sanmove=%s" % ( 163 id(self), self, ply, lastmove)) 164 165 try: 166 move = parseSAN(board1, lastmove) 167 log.debug("ICPlayer.makemove: id(self)=%d self=%s parsed move=%s" % ( 168 id(self), self, move)) 169 except ParsingError: 170 raise 171 return move 172 finally: 173 log.debug("ICPlayer.makemove: id(self)=%d self=%s returning move=%s" % (id(self), self, move)) 174 175 # Interacting with the player 176 177 def pause(self): 178 pass 179 180 def resume(self): 181 pass 182 183 def setBoard(self, fen): 184 # setBoard will currently only be called for ServerPlayer when starting 185 # to observe some game. In this case FICS already knows how the board 186 # should look, and we don't need to set anything 187 pass 188 189 def playerUndoMoves(self, movecount, gamemodel): 190 log.debug("ICPlayer.playerUndoMoves: id(self)=%d self=%s, undoing movecount=%d" % ( 191 id(self), self, movecount)) 192 # If current player has changed so that it is no longer us to move, 193 # We raise TurnInterrupt in order to let GameModel continue the game 194 if movecount % 2 == 1 and gamemodel.curplayer != self: 195 log.debug("ICPlayer.playerUndoMoves: set self.turn_interrupt = True %s" % self.name) 196 if self.connection.ICC: 197 self.move_queue.put_nowait("stm") 198 else: 199 self.turn_interrupt = True 200 if movecount % 2 == 0 and gamemodel.curplayer == self: 201 log.debug("ICPlayer.playerUndoMoves: set self.pass_interrupt = True %s" % self.name) 202 if self.connection.ICC: 203 self.move_queue.put_nowait("pass") 204 else: 205 self.pass_interrupt = True 206 207 def resetPosition(self): 208 """ Used in observed examined games f.e. when LectureBot starts another example""" 209 self.turn_interrupt = True 210 211 def putMessage(self, text): 212 self.connection.cm.tellPlayer(self.ichandle, text) 213 214 # Offer handling 215 216 def offerRematch(self): 217 if self.gamemodel.timed: 218 minimum = int(self.gamemodel.timemodel.intervals[0][0]) / 60 219 inc = self.gamemodel.timemodel.gain 220 else: 221 minimum = 0 222 inc = 0 223 self.connection.om.challenge(self.ichandle, 224 self.gamemodel.ficsgame.game_type, minimum, 225 inc, self.gamemodel.ficsgame.rated) 226 227 def offer(self, offer): 228 log.debug("ICPlayer.offer: self=%s %s" % (repr(self), offer)) 229 if offer.type == TAKEBACK_OFFER: 230 # only 1 outstanding takeback offer allowed on FICS, so remove any of ours 231 for index in list(self.offers.keys()): 232 if self.offers[index].type == TAKEBACK_OFFER: 233 log.debug("ICPlayer.offer: del self.offers[%s] %s" % (index, offer)) 234 del self.offers[index] 235 self.connection.om.offer(offer) 236 237 def offerDeclined(self, offer): 238 log.debug("ICPlayer.offerDeclined: sending decline for %s" % offer) 239 self.connection.om.decline(offer) 240 241 def offerWithdrawn(self, offer): 242 pass 243 244 def offerError(self, offer, error): 245 pass 246 247 def observe(self): 248 self.connection.client.run_command("observe %s" % self.ichandle) 249