def onPlayGameCreated(self, matchlist): log.debug( "'%s' '%s' '%s'" % (matchlist[0].string, matchlist[1].string, matchlist[-1].string), extra={"task": (self.connection.username, "BM.onPlayGameCreated")}) wname, wrating, bname, brating, rated, match_type, minutes, inc = matchlist[ 0].groups() item = 2 if self.connection.USCN else 1 gameno, wname, bname, rated, match_type = matchlist[item].groups() gameno = int(gameno) wrating = parseRating(wrating) brating = parseRating(brating) rated = rated == "rated" game_type = GAME_TYPES[match_type] wplayer = self.connection.players.get(wname) bplayer = self.connection.players.get(bname) for player, rating in ((wplayer, wrating), (bplayer, brating)): if game_type.rating_type in player.ratings and \ player.ratings[game_type.rating_type] != rating: player.ratings[game_type.rating_type] = rating player.emit("ratings_changed", game_type.rating_type, player) style12 = matchlist[-1].groups()[0] castleSigns = self.generateCastleSigns(style12, game_type) self.castleSigns[gameno] = castleSigns gameno, relation, curcol, ply, wname, bname, wms, bms, gain, lastmove, fen = \ self.parseStyle12(style12, castleSigns) game = FICSGame(wplayer, bplayer, gameno=gameno, rated=rated, game_type=game_type, minutes=int(minutes), inc=int(inc), board=FICSBoard(wms, bms, fen=fen)) game = self.connection.games.get(game) for player in (wplayer, bplayer): if player.status != IC_STATUS_PLAYING: player.status = IC_STATUS_PLAYING if player.game != game: player.game = game self.theGameImPlaying = game self.gamemodelStartedEvents[gameno] = threading.Event() self.connection.client.run_command("follow") self.emit("playGameCreated", game)
def onPlayGameCreated(self, matchlist): log.debug( "'%s' '%s' '%s'" % (matchlist[0].string, matchlist[1].string, matchlist[-1].string), extra={"task": (self.connection.username, "BM.onPlayGameCreated")}) wname, wrating, bname, brating, rated, match_type, minutes, inc = matchlist[ 0].groups() item = 2 if self.connection.USCN else 1 gameno, wname, bname, rated, match_type = matchlist[item].groups() gameno = int(gameno) wrating = parseRating(wrating) brating = parseRating(brating) rated = rated == "rated" game_type = GAME_TYPES[match_type] wplayer = self.connection.players.get(wname) bplayer = self.connection.players.get(bname) for player, rating in ((wplayer, wrating), (bplayer, brating)): if game_type.rating_type in player.ratings and \ player.ratings[game_type.rating_type] != rating: player.ratings[game_type.rating_type] = rating player.emit("ratings_changed", game_type.rating_type, player) style12 = matchlist[-1].groups()[0] castleSigns = self.generateCastleSigns(style12, game_type) self.castleSigns[gameno] = castleSigns gameno, relation, curcol, ply, wname, bname, wms, bms, gain, lastmove, fen = \ self.parseStyle12(style12, castleSigns) game = FICSGame(wplayer, bplayer, gameno=gameno, rated=rated, game_type=game_type, minutes=int(minutes), inc=int(inc), board=FICSBoard(wms, bms, fen=fen)) game = self.connection.games.get(game) for player in (wplayer, bplayer): if player.status != IC_STATUS_PLAYING: player.status = IC_STATUS_PLAYING if player.game != game: player.game = game self.theGameImPlaying = game self.gamemodelStartedEvents[gameno] = threading.Event() self.connection.client.run_command("follow") self.emit("playGameCreated", game)
def on_player_who(self, match): for blitz, status, name, titles in whomatch_re.findall(match.string): player = self.connection.players.get(name) if not player.online: player.online = True status = STATUS[status] if player.status != status: player.status = status titles = self.parseTitles(titles) if not player.titles >= titles: player.titles |= titles blitz = parseRating(blitz) if player.ratings[TYPE_BLITZ] != blitz: player.ratings[TYPE_BLITZ] = blitz player.emit("ratings_changed", TYPE_BLITZ, player)
def on_player_who(self, match): for blitz, status, name, titles in whomatch_re.findall(match.string): player = self.connection.players.get(name) if not player.online: player.online = True status = STATUS[status] if player.status != status: player.status = status titles = self.parseTitles(titles) if not player.titles >= titles: player.titles |= titles blitz = parseRating(blitz) if player.ratings[TYPE_BLITZ] != blitz: player.ratings[TYPE_BLITZ] = blitz player.emit("ratings_changed", TYPE_BLITZ, player)
def on_game_list(self, matchlist): games = [] for match in matchlist[:-1]: if isinstance(match, str): if match: parts0, parts1 = match.split("[") gameno, wrating, wname, brating, bname = parts0.split() private = parts1[0] shorttype = parts1[1] rated = parts1[2] min = parts1[3:6] inc = parts1[7:10] else: continue else: gameno, wrating, wname, brating, bname, private, shorttype, rated, min, inc, whour, wmin, wsec, bhour, bmin, bsec, wmat, bmat, color, movno = ( match.groups()) try: gametype = GAME_TYPES_BY_SHORT_FICS_NAME[shorttype] except KeyError: return wplayer = self.connection.players.get(wname) bplayer = self.connection.players.get(bname) game = FICSGame( wplayer, bplayer, gameno=int(gameno), rated=(rated == "r"), private=(private == "p"), minutes=int(min), inc=int(inc), game_type=gametype, ) for player, rating in ((wplayer, wrating), (bplayer, brating)): if player.status != IC_STATUS_PLAYING: player.status = IC_STATUS_PLAYING if player.game != game: player.game = game rating = parseRating(rating) if player.ratings[gametype.rating_type] != rating: player.ratings[gametype.rating_type] = rating player.emit("ratings_changed", gametype.rating_type, player) game = self.connection.games.get(game, emit=False) games.append(game) self.connection.games.emit("FICSGameCreated", games)
def on_game_list(self, matchlist): games = [] for match in matchlist[:-1]: if isinstance(match, str): if match: parts0, parts1 = match.split("[") gameno, wrating, wname, brating, bname = parts0.split() private = parts1[0] shorttype = parts1[1] rated = parts1[2] min = parts1[3:6] inc = parts1[7:10] else: continue else: gameno, wrating, wname, brating, bname, private, shorttype, rated, min, \ inc, whour, wmin, wsec, bhour, bmin, bsec, wmat, bmat, color, movno = match.groups() try: gametype = GAME_TYPES_BY_SHORT_FICS_NAME[shorttype] except KeyError: return wplayer = self.connection.players.get(wname) bplayer = self.connection.players.get(bname) game = FICSGame(wplayer, bplayer, gameno=int(gameno), rated=(rated == "r"), private=(private == "p"), minutes=int(min), inc=int(inc), game_type=gametype) for player, rating in ((wplayer, wrating), (bplayer, brating)): if player.status != IC_STATUS_PLAYING: player.status = IC_STATUS_PLAYING if player.game != game: player.game = game rating = parseRating(rating) if gametype.rating_type in player.ratings and \ player.ratings[gametype.rating_type] != rating: player.ratings[gametype.rating_type] = rating player.emit("ratings_changed", gametype.rating_type, player) game = self.connection.games.get(game, emit=False) games.append(game) self.connection.games.emit("FICSGameCreated", games)
def on_player_available(self, matches): name, titles, blitz, std, wild, light, bughouse = matches[0].groups() player = self.connection.players.get(name) if not player.online: player.online = True if player.status != IC_STATUS_AVAILABLE: player.status = IC_STATUS_AVAILABLE titles = self.parseTitles(titles) if not player.titles >= titles: player.titles |= titles for rating_type, rating in ((TYPE_BLITZ, blitz), (TYPE_STANDARD, std), (TYPE_LIGHTNING, light), (TYPE_WILD, wild), (TYPE_BUGHOUSE, bughouse)): rating = parseRating(rating) if player.ratings[rating_type] != rating: player.ratings[rating_type] = rating player.emit("ratings_changed", rating_type, player)
def on_player_available(self, matches): name, titles, blitz, std, wild, light, bughouse = matches[0].groups() player = self.connection.players.get(name) if not player.online: player.online = True if player.status != IC_STATUS_AVAILABLE: player.status = IC_STATUS_AVAILABLE titles = self.parseTitles(titles) if not player.titles >= titles: player.titles |= titles for rating_type, rating in ((TYPE_BLITZ, blitz), (TYPE_STANDARD, std), (TYPE_LIGHTNING, light), (TYPE_WILD, wild), (TYPE_BUGHOUSE, bughouse)): rating = parseRating(rating) if player.ratings[rating_type] != rating: player.ratings[rating_type] = rating player.emit("ratings_changed", rating_type, player)
def on_player_connectI(self, match, set_online=True): # bslwBzSLx # gbtami 001411E1663P1483P1720P0P1646P0P0P1679P name, status, titlehex, blitz, blitzdev, std, stddev, light, lightdev, wild, wilddev, bughouse, bughousedev, crazyhouse, crazyhousedev, suicide, suicidedev, losers, losersdev, atomic, atomicdev = ( match.groups()) player = self.connection.players.get(name) titles = parse_title_hex(titlehex) if not player.titles >= titles: player.titles |= titles for rating_type, elo, dev in ( (TYPE_BLITZ, blitz, blitzdev), (TYPE_STANDARD, std, stddev), (TYPE_LIGHTNING, light, lightdev), (TYPE_ATOMIC, atomic, atomicdev), (TYPE_WILD, wild, wilddev), (TYPE_CRAZYHOUSE, crazyhouse, crazyhousedev), (TYPE_BUGHOUSE, bughouse, bughousedev), (TYPE_LOSERS, losers, losersdev), (TYPE_SUICIDE, suicide, suicidedev), ): rating = parseRating(elo) if player.ratings[rating_type] != rating: player.ratings[rating_type] = rating player.emit("ratings_changed", rating_type, player) player.deviations[rating_type] = DEVIATION[dev] # do last so rating info is there when notifications are generated status = STATUS[status] if player.status != status: player.status = status if set_online and not player.online: player.online = True return player
def on_player_connectI(self, match, set_online=True): # bslwBzSLx # gbtami 001411E1663P1483P1720P0P1646P0P0P1679P name, status, titlehex, blitz, blitzdev, std, stddev, light, lightdev, \ wild, wilddev, bughouse, bughousedev, crazyhouse, crazyhousedev, \ suicide, suicidedev, losers, losersdev, atomic, atomicdev = match.groups() player = self.connection.players.get(name) titles = parse_title_hex(titlehex) if not player.titles >= titles: player.titles |= titles for rating_type, elo, dev in \ ((TYPE_BLITZ, blitz, blitzdev), (TYPE_STANDARD, std, stddev), (TYPE_LIGHTNING, light, lightdev), (TYPE_ATOMIC, atomic, atomicdev), (TYPE_WILD, wild, wilddev), (TYPE_CRAZYHOUSE, crazyhouse, crazyhousedev), (TYPE_BUGHOUSE, bughouse, bughousedev), (TYPE_LOSERS, losers, losersdev), (TYPE_SUICIDE, suicide, suicidedev)): rating = parseRating(elo) if player.ratings[rating_type] != rating: player.ratings[rating_type] = rating player.emit("ratings_changed", rating_type, player) player.deviations[rating_type] = DEVIATION[dev] # do last so rating info is there when notifications are generated status = STATUS[status] if player.status != status: player.status = status if set_online and not player.online: player.online = True return player
def onObserveGameCreated(self, matchlist): log.debug("'%s'" % (matchlist[1].string), extra={ "task": (self.connection.username, "BM.onObserveGameCreated") }) if self.connection.USCN: # TODO? is this ok? game_type = GAME_TYPES["blitz"] castleSigns = ("k", "q") else: gameno, wname, wrating, bname, brating, rated, gametype, minutes, inc = matchlist[ 1].groups() wrating = parseRating(wrating) brating = parseRating(brating) game_type = GAME_TYPES[gametype] style12 = matchlist[-1].groups()[0] castleSigns = self.generateCastleSigns(style12, game_type) gameno, relation, curcol, ply, wname, bname, wms, bms, gain, lastmove, fen = \ self.parseStyle12(style12, castleSigns) gameno = int(gameno) self.castleSigns[gameno] = castleSigns wplayer = self.connection.players.get(wname) bplayer = self.connection.players.get(bname) if relation == IC_POS_OBSERVING_EXAMINATION: pgnHead = [("Event", "FICS %s %s game" % (rated, game_type.fics_name)), ("Site", "freechess.org"), ("White", wname), ("Black", bname), ("Result", "*"), ("SetUp", "1"), ("FEN", fen)] pgn = "\n".join(['[%s "%s"]' % line for line in pgnHead]) + "\n*\n" game = FICSGame(wplayer, bplayer, gameno=gameno, rated=rated == "rated", game_type=game_type, minutes=int(minutes), inc=int(inc), board=FICSBoard(wms, bms, pgn=pgn), relation=relation) game = self.connection.games.get(game) self.gamesImObserving[game] = wms, bms self.gamemodelStartedEvents[game.gameno] = threading.Event() self.emit("obsGameCreated", game) self.gamemodelStartedEvents[game.gameno].wait() else: game = FICSGame(wplayer, bplayer, gameno=gameno, rated=rated == "rated", game_type=game_type, minutes=int(minutes), inc=int(inc), relation=relation) game = self.connection.games.get(game, emit=False) if not game.supported: log.warning("Trying to follow an unsupported type game %s" % game.game_type) return if game.gameno in self.gamemodelStartedEvents: log.warning("%s already in gamemodelstartedevents" % game.gameno) return self.gamesImObserving[game] = wms, bms self.queuedStyle12s[game.gameno] = [] self.queuedEmits[game.gameno] = [] self.gamemodelStartedEvents[game.gameno] = threading.Event() # FICS doesn't send the move list after 'observe' and 'follow' commands self.connection.client.run_command("moves %d" % game.gameno)
def parseGame(self, matchlist, gameclass, in_progress=False, gameno=None): """ Parses the header and movelist for an observed or stored game from its matchlist (an re.match object) into a gameclass (FICSGame or subclass of) object. in_progress - should be True for an observed game matchlist, and False for stored/adjourned games """ # ################ observed game movelist example: # Movelist for game 64: # # Ajido (2281) vs. IMgooeyjim (2068) --- Thu Oct 14, 20:36 PDT 2010 # Rated standard match, initial time: 15 minutes, increment: 3 seconds. # # Move Ajido IMgooeyjim # ---- --------------------- --------------------- # 1. d4 (0:00.000) Nf6 (0:00.000) # 2. c4 (0:04.061) g6 (0:00.969) # 3. Nc3 (0:13.280) Bg7 (0:06.422) # {Still in progress} * # # ################# stored game example: # BwanaSlei (1137) vs. mgatto (1336) --- Wed Nov 5, 20:56 PST 2008 # Rated blitz match, initial time: 5 minutes, increment: 0 seconds. # # Move BwanaSlei mgatto # ---- --------------------- --------------------- # 1. e4 (0:00.000) c5 (0:00.000) # 2. d4 (0:05.750) cxd4 (0:03.020) # ... # 23. Qxf3 (1:05.500) # {White lost connection; game adjourned} * # # ################# stored wild/3 game with style12: # kurushi (1626) vs. mgatto (1627) --- Thu Nov 4, 10:33 PDT 2010 # Rated wild/3 match, initial time: 3 minutes, increment: 0 seconds. # # <12> nqbrknrn pppppppp -------- -------- -------- -------- PPPPPPPP NQBRKNRN W -1 0 0 0 0 0 17 kurushi mgatto -4 3 0 39 39 169403 45227 1 none (0:00.000) none 0 1 0 # # Move kurushi mgatto # ---- --------------------- --------------------- # 1. Nb3 (0:00.000) d5 (0:00.000) # 2. Nhg3 (0:00.386) e5 (0:03.672) # ... # 28. Rxd5 (0:00.412) # {Black lost connection; game adjourned} * # # ################# stored game movelist following stored game(s): # Stored games for mgatto: # C Opponent On Type Str M ECO Date # 1: W BabyLurking Y [ br 5 0] 29-13 W27 D37 Fri Nov 5, 04:41 PDT 2010 # 2: W gbtami N [ wr 5 0] 32-34 W14 --- Thu Oct 21, 00:14 PDT 2010 # # mgatto (1233) vs. BabyLurking (1455) --- Fri Nov 5, 04:33 PDT 2010 # Rated blitz match, initial time: 5 minutes, increment: 0 seconds. # # Move mgatto BabyLurking # ---- ---------------- ---------------- # 1. Nf3 (0:00) d5 (0:00) # 2. d4 (0:03) Nf6 (0:00) # 3. c4 (0:03) e6 (0:00) # {White lost connection; game adjourned} * # # ################## stored game movelist following stored game(s): # ## Note: A wild stored game in this format won't be parseable into a board because # ## it doesn't come with a style12 that has the start position, so we warn and return # ################## # Stored games for mgatto: # C Opponent On Type Str M ECO Date # 1: W gbtami N [ wr 5 0] 32-34 W14 --- Thu Oct 21, 00:14 PDT 2010 # # mgatto (1627) vs. gbtami (1881) --- Thu Oct 21, 00:10 PDT 2010 # Rated wild/fr match, initial time: 5 minutes, increment: 0 seconds. # # Move mgatto gbtami # ---- ---------------- ---------------- # 1. d4 (0:00) b6 (0:00) # 2. b3 (0:06) d5 (0:03) # 3. c4 (0:08) e6 (0:03) # 4. e3 (0:04) dxc4 (0:02) # 5. bxc4 (0:02) g6 (0:09) # 6. Nd3 (0:12) Bg7 (0:02) # 7. Nc3 (0:10) Ne7 (0:03) # 8. Be2 (0:08) c5 (0:05) # 9. a4 (0:07) cxd4 (0:38) # 10. exd4 (0:06) Bxd4 (0:03) # 11. O-O (0:10) Qc6 (0:06) # 12. Bf3 (0:16) Qxc4 (0:04) # 13. Bxa8 (0:03) Rxa8 (0:14) # {White lost connection; game adjourned} * # # ################# other reasons the game could be stored/adjourned: # Game courtesyadjourned by (Black|White) # Still in progress # This one must be a FICS bug # Game adjourned by mutual agreement # (White|Black) lost connection; game adjourned # Game adjourned by ((server shutdown)|(adjudication)|(simul holder)) index = 0 if in_progress: gameno = int(matchlist[index].groups()[0]) index += 2 header1 = matchlist[index] if isinstance(matchlist[index], str) \ else matchlist[index].group() matches = moveListHeader1.match(header1).groups() wname, wrating, bname, brating = matches[:4] if self.connection.FatICS: year, month, day, hour, minute, timezone = matches[11:] else: weekday, month, day, hour, minute, timezone, year = matches[4:11] month = months.index(month) + 1 wrating = parseRating(wrating) brating = parseRating(brating) rated, game_type, minutes, increment = \ moveListHeader2.match(matchlist[index + 1]).groups() minutes = int(minutes) increment = int(increment) game_type = GAME_TYPES[game_type] reason = matchlist[-1].group().lower() if in_progress: result = None result_str = "*" elif "1-0" in reason: result = WHITEWON result_str = "1-0" elif "0-1" in reason: result = BLACKWON result_str = "0-1" elif "1/2-1/2" in reason: result = DRAW result_str = "1/2-1/2" else: result = ADJOURNED result_str = "*" result, reason = parse_reason(result, reason, wname=wname) index += 3 if matchlist[index].startswith("<12>"): style12 = matchlist[index][5:] castleSigns = self.generateCastleSigns(style12, game_type) gameno, relation, curcol, ply, wname, bname, wms, bms, gain, lastmove, \ fen = self.parseStyle12(style12, castleSigns) initialfen = fen movesstart = index + 4 else: if game_type.rating_type == TYPE_WILD: # we need a style12 start position to correctly parse a wild/* board log.error("BoardManager.parseGame: no style12 for %s board." % game_type.fics_name) return None castleSigns = ("k", "q") initialfen = None movesstart = index + 2 if in_progress: self.castleSigns[gameno] = castleSigns moves = {} times = {} wms = bms = minutes * 60 * 1000 for line in matchlist[movesstart:-1]: if not moveListMoves.match(line): log.error("BoardManager.parseGame: unmatched line: \"%s\"" % repr(line)) raise Exception( "BoardManager.parseGame: unmatched line: \"%s\"" % repr(line)) moveno, wmove, whour, wmin, wsec, wmsec, bmove, bhour, bmin, bsec, bmsec = \ moveListMoves.match(line).groups() whour = 0 if whour is None else int(whour[0]) bhour = 0 if bhour is None else int(bhour[0]) ply = int(moveno) * 2 - 2 if wmove: moves[ply] = wmove wms -= (int(whour) * 60 * 60 * 1000) + (int(wmin) * 60 * 1000) + (int(wsec) * 1000) if wmsec is not None: wms -= int(wmsec) else: wmsec = 0 if increment > 0: wms += (increment * 1000) times[ply] = "%01d:%02d:%02d.%03d" % (int(whour), int(wmin), int(wsec), int(wmsec)) if bmove: moves[ply + 1] = bmove bms -= (int(bhour) * 60 * 60 * 1000) + (int(bmin) * 60 * 1000) + (int(bsec) * 1000) if bmsec is not None: bms -= int(bmsec) else: bmsec = 0 if increment > 0: bms += (increment * 1000) times[ply + 1] = "%01d:%02d:%02d.%03d" % (int(bhour), int(bmin), int(bsec), int(bmsec)) if in_progress and gameno in self.queuedStyle12s: # Apply queued board updates for style12 in self.queuedStyle12s[gameno]: gameno, relation, curcol, ply, wname, bname, wms, bms, gain, lastmove, fen = \ self.parseStyle12(style12, castleSigns) if lastmove is None: continue moves[ply - 1] = lastmove # Updated the queuedMoves in case there has been a takeback for moveply in list(moves.keys()): if moveply > ply - 1: del moves[moveply] del self.queuedStyle12s[gameno] pgnHead = [ ("Event", "FICS %s %s game" % (rated.lower(), game_type.fics_name)), ("Site", "freechess.org"), ("White", wname), ("Black", bname), ("TimeControl", "%d+%d" % (minutes * 60, increment)), ("Result", result_str), ("WhiteClock", msToClockTimeTag(wms)), ("BlackClock", msToClockTimeTag(bms)), ] if wrating != 0: pgnHead += [("WhiteElo", wrating)] if brating != 0: pgnHead += [("BlackElo", brating)] if year and month and day and hour and minute: pgnHead += [ ("Date", "%04d.%02d.%02d" % (int(year), int(month), int(day))), ("Time", "%02d:%02d:00" % (int(hour), int(minute))), ] if initialfen: pgnHead += [("SetUp", "1"), ("FEN", initialfen)] if game_type.variant_type == FISCHERRANDOMCHESS: pgnHead += [("Variant", "Fischerandom")] # FR is the only variant used in this tag by the PGN generator @ # ficsgames.org. They put all the other wild/* stuff only in the # "Event" header. elif game_type.variant_type == CRAZYHOUSECHESS: pgnHead += [("Variant", "Crazyhouse")] elif game_type.variant_type in (WILDCASTLECHESS, WILDCASTLESHUFFLECHESS): pgnHead += [("Variant", "Wildcastle")] elif game_type.variant_type == ATOMICCHESS: pgnHead += [("Variant", "Atomic")] elif game_type.variant_type == LOSERSCHESS: pgnHead += [("Variant", "Losers")] elif game_type.variant_type == SUICIDECHESS: pgnHead += [("Variant", "Suicide")] pgn = "\n".join(['[%s "%s"]' % line for line in pgnHead]) + "\n" moves = sorted(moves.items()) for ply, move in moves: if ply % 2 == 0: pgn += "%d. " % (ply // 2 + 1) time = times[ply] pgn += "%s {[%%emt %s]} " % (move, time) pgn += "*\n" wplayer = self.connection.players.get(wname) bplayer = self.connection.players.get(bname) for player, rating in ((wplayer, wrating), (bplayer, brating)): if game_type.rating_type in player.ratings and \ player.ratings[game_type.rating_type] != rating: player.ratings[game_type.rating_type] = rating player.emit("ratings_changed", game_type.rating_type, player) game = gameclass(wplayer, bplayer, game_type=game_type, result=result, rated=(rated.lower() == "rated"), minutes=minutes, inc=increment, board=FICSBoard(wms, bms, pgn=pgn)) if in_progress: game.gameno = gameno else: if gameno is not None: game.gameno = gameno game.reason = reason game = self.connection.games.get(game, emit=False) return game
def onObserveGameCreated(self, matchlist): log.debug("'%s'" % (matchlist[1].string), extra={"task": (self.connection.username, "BM.onObserveGameCreated")}) if self.connection.USCN: # TODO? is this ok? game_type = GAME_TYPES["blitz"] castleSigns = ("k", "q") else: gameno, wname, wrating, bname, brating, rated, gametype, minutes, inc = matchlist[ 1].groups() wrating = parseRating(wrating) brating = parseRating(brating) game_type = GAME_TYPES[gametype] style12 = matchlist[-1].groups()[0] castleSigns = self.generateCastleSigns(style12, game_type) gameno, relation, curcol, ply, wname, bname, wms, bms, gain, lastmove, fen = \ self.parseStyle12(style12, castleSigns) gameno = int(gameno) self.castleSigns[gameno] = castleSigns wplayer = self.connection.players.get(wname) bplayer = self.connection.players.get(bname) if relation == IC_POS_OBSERVING_EXAMINATION: pgnHead = [ ("Event", "FICS %s %s game" % (rated, game_type.fics_name)), ("Site", "freechess.org"), ("White", wname), ("Black", bname), ("Result", "*"), ("SetUp", "1"), ("FEN", fen) ] pgn = "\n".join(['[%s "%s"]' % line for line in pgnHead]) + "\n*\n" game = FICSGame(wplayer, bplayer, gameno=gameno, rated=rated == "rated", game_type=game_type, minutes=int(minutes), inc=int(inc), board=FICSBoard(wms, bms, pgn=pgn), relation=relation) game = self.connection.games.get(game) self.gamesImObserving[game] = wms, bms self.gamemodelStartedEvents[game.gameno] = threading.Event() self.emit("obsGameCreated", game) self.gamemodelStartedEvents[game.gameno].wait() else: game = FICSGame(wplayer, bplayer, gameno=gameno, rated=rated == "rated", game_type=game_type, minutes=int(minutes), inc=int(inc), relation=relation) game = self.connection.games.get(game, emit=False) if not game.supported: log.warning("Trying to follow an unsupported type game %s" % game.game_type) return if game.gameno in self.gamemodelStartedEvents: log.warning("%s already in gamemodelstartedevents" % game.gameno) return self.gamesImObserving[game] = wms, bms self.queuedStyle12s[game.gameno] = [] self.queuedEmits[game.gameno] = [] self.gamemodelStartedEvents[game.gameno] = threading.Event() # FICS doesn't send the move list after 'observe' and 'follow' commands self.connection.client.run_command("moves %d" % game.gameno)
def parseGame(self, matchlist, gameclass, in_progress=False, gameno=None): """ Parses the header and movelist for an observed or stored game from its matchlist (an re.match object) into a gameclass (FICSGame or subclass of) object. in_progress - should be True for an observed game matchlist, and False for stored/adjourned games """ # ################ observed game movelist example: # Movelist for game 64: # # Ajido (2281) vs. IMgooeyjim (2068) --- Thu Oct 14, 20:36 PDT 2010 # Rated standard match, initial time: 15 minutes, increment: 3 seconds. # # Move Ajido IMgooeyjim # ---- --------------------- --------------------- # 1. d4 (0:00.000) Nf6 (0:00.000) # 2. c4 (0:04.061) g6 (0:00.969) # 3. Nc3 (0:13.280) Bg7 (0:06.422) # {Still in progress} * # # ################# stored game example: # BwanaSlei (1137) vs. mgatto (1336) --- Wed Nov 5, 20:56 PST 2008 # Rated blitz match, initial time: 5 minutes, increment: 0 seconds. # # Move BwanaSlei mgatto # ---- --------------------- --------------------- # 1. e4 (0:00.000) c5 (0:00.000) # 2. d4 (0:05.750) cxd4 (0:03.020) # ... # 23. Qxf3 (1:05.500) # {White lost connection; game adjourned} * # # ################# stored wild/3 game with style12: # kurushi (1626) vs. mgatto (1627) --- Thu Nov 4, 10:33 PDT 2010 # Rated wild/3 match, initial time: 3 minutes, increment: 0 seconds. # # <12> nqbrknrn pppppppp -------- -------- -------- -------- PPPPPPPP NQBRKNRN W -1 0 0 0 0 0 17 kurushi mgatto -4 3 0 39 39 169403 45227 1 none (0:00.000) none 0 1 0 # # Move kurushi mgatto # ---- --------------------- --------------------- # 1. Nb3 (0:00.000) d5 (0:00.000) # 2. Nhg3 (0:00.386) e5 (0:03.672) # ... # 28. Rxd5 (0:00.412) # {Black lost connection; game adjourned} * # # ################# stored game movelist following stored game(s): # Stored games for mgatto: # C Opponent On Type Str M ECO Date # 1: W BabyLurking Y [ br 5 0] 29-13 W27 D37 Fri Nov 5, 04:41 PDT 2010 # 2: W gbtami N [ wr 5 0] 32-34 W14 --- Thu Oct 21, 00:14 PDT 2010 # # mgatto (1233) vs. BabyLurking (1455) --- Fri Nov 5, 04:33 PDT 2010 # Rated blitz match, initial time: 5 minutes, increment: 0 seconds. # # Move mgatto BabyLurking # ---- ---------------- ---------------- # 1. Nf3 (0:00) d5 (0:00) # 2. d4 (0:03) Nf6 (0:00) # 3. c4 (0:03) e6 (0:00) # {White lost connection; game adjourned} * # # ################## stored game movelist following stored game(s): # ## Note: A wild stored game in this format won't be parseable into a board because # ## it doesn't come with a style12 that has the start position, so we warn and return # ################## # Stored games for mgatto: # C Opponent On Type Str M ECO Date # 1: W gbtami N [ wr 5 0] 32-34 W14 --- Thu Oct 21, 00:14 PDT 2010 # # mgatto (1627) vs. gbtami (1881) --- Thu Oct 21, 00:10 PDT 2010 # Rated wild/fr match, initial time: 5 minutes, increment: 0 seconds. # # Move mgatto gbtami # ---- ---------------- ---------------- # 1. d4 (0:00) b6 (0:00) # 2. b3 (0:06) d5 (0:03) # 3. c4 (0:08) e6 (0:03) # 4. e3 (0:04) dxc4 (0:02) # 5. bxc4 (0:02) g6 (0:09) # 6. Nd3 (0:12) Bg7 (0:02) # 7. Nc3 (0:10) Ne7 (0:03) # 8. Be2 (0:08) c5 (0:05) # 9. a4 (0:07) cxd4 (0:38) # 10. exd4 (0:06) Bxd4 (0:03) # 11. O-O (0:10) Qc6 (0:06) # 12. Bf3 (0:16) Qxc4 (0:04) # 13. Bxa8 (0:03) Rxa8 (0:14) # {White lost connection; game adjourned} * # # ################# other reasons the game could be stored/adjourned: # Game courtesyadjourned by (Black|White) # Still in progress # This one must be a FICS bug # Game adjourned by mutual agreement # (White|Black) lost connection; game adjourned # Game adjourned by ((server shutdown)|(adjudication)|(simul holder)) index = 0 if in_progress: gameno = int(matchlist[index].groups()[0]) index += 2 header1 = matchlist[index] if isinstance(matchlist[index], str) \ else matchlist[index].group() matches = moveListHeader1.match(header1).groups() wname, wrating, bname, brating = matches[:4] if self.connection.FatICS: year, month, day, hour, minute, timezone = matches[11:] else: weekday, month, day, hour, minute, timezone, year = matches[4:11] month = months.index(month) + 1 wrating = parseRating(wrating) brating = parseRating(brating) rated, game_type, minutes, increment = \ moveListHeader2.match(matchlist[index + 1]).groups() minutes = int(minutes) increment = int(increment) game_type = GAME_TYPES[game_type] reason = matchlist[-1].group().lower() if in_progress: result = None result_str = "*" elif "1-0" in reason: result = WHITEWON result_str = "1-0" elif "0-1" in reason: result = BLACKWON result_str = "0-1" elif "1/2-1/2" in reason: result = DRAW result_str = "1/2-1/2" else: result = ADJOURNED result_str = "*" result, reason = parse_reason(result, reason, wname=wname) index += 3 if matchlist[index].startswith("<12>"): style12 = matchlist[index][5:] castleSigns = self.generateCastleSigns(style12, game_type) gameno, relation, curcol, ply, wname, bname, wms, bms, gain, lastmove, \ fen = self.parseStyle12(style12, castleSigns) initialfen = fen movesstart = index + 4 else: if game_type.rating_type == TYPE_WILD: # we need a style12 start position to correctly parse a wild/* board log.error("BoardManager.parseGame: no style12 for %s board." % game_type.fics_name) return None castleSigns = ("k", "q") initialfen = None movesstart = index + 2 if in_progress: self.castleSigns[gameno] = castleSigns moves = {} times = {} wms = bms = minutes * 60 * 1000 for line in matchlist[movesstart:-1]: if not moveListMoves.match(line): log.error("BoardManager.parseGame: unmatched line: \"%s\"" % repr(line)) raise Exception("BoardManager.parseGame: unmatched line: \"%s\"" % repr(line)) moveno, wmove, whour, wmin, wsec, wmsec, bmove, bhour, bmin, bsec, bmsec = \ moveListMoves.match(line).groups() whour = 0 if whour is None else int(whour[0]) bhour = 0 if bhour is None else int(bhour[0]) ply = int(moveno) * 2 - 2 if wmove: moves[ply] = wmove wms -= (int(whour) * 60 * 60 * 1000) + ( int(wmin) * 60 * 1000) + (int(wsec) * 1000) if wmsec is not None: wms -= int(wmsec) else: wmsec = 0 if increment > 0: wms += (increment * 1000) times[ply] = "%01d:%02d:%02d.%03d" % (int(whour), int(wmin), int(wsec), int(wmsec)) if bmove: moves[ply + 1] = bmove bms -= (int(bhour) * 60 * 60 * 1000) + ( int(bmin) * 60 * 1000) + (int(bsec) * 1000) if bmsec is not None: bms -= int(bmsec) else: bmsec = 0 if increment > 0: bms += (increment * 1000) times[ply + 1] = "%01d:%02d:%02d.%03d" % ( int(bhour), int(bmin), int(bsec), int(bmsec)) if in_progress and gameno in self.queuedStyle12s: # Apply queued board updates for style12 in self.queuedStyle12s[gameno]: gameno, relation, curcol, ply, wname, bname, wms, bms, gain, lastmove, fen = \ self.parseStyle12(style12, castleSigns) if lastmove is None: continue moves[ply - 1] = lastmove # Updated the queuedMoves in case there has been a takeback for moveply in list(moves.keys()): if moveply > ply - 1: del moves[moveply] del self.queuedStyle12s[gameno] pgnHead = [ ("Event", "FICS %s %s game" % (rated.lower(), game_type.fics_name)), ("Site", "freechess.org"), ("White", wname), ("Black", bname), ("TimeControl", "%d+%d" % (minutes * 60, increment)), ("Result", result_str), ("WhiteClock", msToClockTimeTag(wms)), ("BlackClock", msToClockTimeTag(bms)), ] if wrating != 0: pgnHead += [("WhiteElo", wrating)] if brating != 0: pgnHead += [("BlackElo", brating)] if year and month and day and hour and minute: pgnHead += [ ("Date", "%04d.%02d.%02d" % (int(year), int(month), int(day))), ("Time", "%02d:%02d:00" % (int(hour), int(minute))), ] if initialfen: pgnHead += [("SetUp", "1"), ("FEN", initialfen)] if game_type.variant_type == FISCHERRANDOMCHESS: pgnHead += [("Variant", "Fischerandom")] # FR is the only variant used in this tag by the PGN generator @ # ficsgames.org. They put all the other wild/* stuff only in the # "Event" header. elif game_type.variant_type == CRAZYHOUSECHESS: pgnHead += [("Variant", "Crazyhouse")] elif game_type.variant_type in (WILDCASTLECHESS, WILDCASTLESHUFFLECHESS): pgnHead += [("Variant", "Wildcastle")] elif game_type.variant_type == ATOMICCHESS: pgnHead += [("Variant", "Atomic")] elif game_type.variant_type == LOSERSCHESS: pgnHead += [("Variant", "Losers")] elif game_type.variant_type == SUICIDECHESS: pgnHead += [("Variant", "Suicide")] pgn = "\n".join(['[%s "%s"]' % line for line in pgnHead]) + "\n" moves = sorted(moves.items()) for ply, move in moves: if ply % 2 == 0: pgn += "%d. " % (ply // 2 + 1) time = times[ply] pgn += "%s {[%%emt %s]} " % (move, time) pgn += "*\n" wplayer = self.connection.players.get(wname) bplayer = self.connection.players.get(bname) for player, rating in ((wplayer, wrating), (bplayer, brating)): if game_type.rating_type in player.ratings and \ player.ratings[game_type.rating_type] != rating: player.ratings[game_type.rating_type] = rating player.emit("ratings_changed", game_type.rating_type, player) game = gameclass(wplayer, bplayer, game_type=game_type, result=result, rated=(rated.lower() == "rated"), minutes=minutes, inc=increment, board=FICSBoard(wms, bms, pgn=pgn)) if in_progress: game.gameno = gameno else: if gameno is not None: game.gameno = gameno game.reason = reason game = self.connection.games.get(game, emit=False) return game
def on_icc_games(self, data): # 1267 guest2504 1400 KQkr(C) 20u 5 12 W: 1 # 1060 guest7400 1489 DeadGuyKai bu 3 0 W: 21 # 791 1506 PlotinusRedux guest3090 bu 2 12 W: 21 # 47 2357 *IM_Danchevski 2683 *GM_Morozevich Ex: scratch W: 35 # 101 Replayer2 Replayer2 Ex: scratch W: 1 # 117 2760 *GM_Topalov 2823 *GM_Caruana Ex: StLouis16 %0 W: 29 # 119 1919 stansai 2068 Agrimont Ex: continuation W: 53 # 456 games displayed (282 played, 174 examined). previous_games = list(self.connection.games.values()) games = [] games_got = [] lines = data.split("\n") for line in lines: # print(line) try: parts = line.split() index = 0 gameno = parts[index] index += 1 if parts[index].isdigit(): wrating = parts[index] index += 1 else: wrating = "----" wname = parts[index] index += 1 if parts[index].isdigit(): brating = parts[index] index += 1 else: brating = "----" bname = parts[index] index += 1 if parts[index] == "Ex:": shorttype = "e" rated = "" min = "0" inc = "0" else: rated = parts[index][-1] shorttype = parts[index][:-1] index += 1 min = parts[index] index += 1 inc = parts[index] private = "" except IndexError: continue try: gametype = GAME_TYPES_BY_SHORT_FICS_NAME[shorttype] except KeyError: # TODO: handle ICC wild types # print("key error in GAME_TYPES_BY_SHORT_FICS_NAME: %s" % shorttype) continue wplayer = self.connection.players.get(wname) bplayer = self.connection.players.get(bname) game = FICSGame( wplayer, bplayer, gameno=int(gameno), rated=(rated == "r"), private=(private == "p"), minutes=int(min), inc=int(inc), game_type=gametype, ) for player, rating in ((wplayer, wrating), (bplayer, brating)): if player.status != IC_STATUS_PLAYING: player.status = IC_STATUS_PLAYING if player.game != game: player.game = game rating = parseRating(rating) if player.ratings[gametype.rating_type] != rating: player.ratings[gametype.rating_type] = rating player.emit("ratings_changed", gametype.rating_type, player) if game not in previous_games: game = self.connection.games.get(game, emit=False) games.append(game) games_got.append(game) for game in previous_games: if (game not in games_got and game not in self.connection.bm.gamesImObserving and game is not self.connection.bm.theGameImPlaying): self.connection.games.game_ended(game) game.wplayer.game = None game.bplayer.game = None self.connection.games.emit("FICSGameCreated", games)
def on_icc_games(self, data): # 1267 guest2504 1400 KQkr(C) 20u 5 12 W: 1 # 1060 guest7400 1489 DeadGuyKai bu 3 0 W: 21 # 791 1506 PlotinusRedux guest3090 bu 2 12 W: 21 # 47 2357 *IM_Danchevski 2683 *GM_Morozevich Ex: scratch W: 35 # 101 Replayer2 Replayer2 Ex: scratch W: 1 # 117 2760 *GM_Topalov 2823 *GM_Caruana Ex: StLouis16 %0 W: 29 # 119 1919 stansai 2068 Agrimont Ex: continuation W: 53 # 456 games displayed (282 played, 174 examined). previous_games = list(self.connection.games.values()) games = [] games_got = [] lines = data.split("\n") for line in lines: # print(line) try: parts = line.split() index = 0 gameno = parts[index] index += 1 if parts[index].isdigit(): wrating = parts[index] index += 1 else: wrating = "----" wname = parts[index] index += 1 if parts[index].isdigit(): brating = parts[index] index += 1 else: brating = "----" bname = parts[index] index += 1 if parts[index] == "Ex:": shorttype = "e" rated = "" min = "0" inc = "0" else: rated = parts[index][-1] shorttype = parts[index][:-1] index += 1 min = parts[index] index += 1 inc = parts[index] private = "" except IndexError: continue try: gametype = GAME_TYPES_BY_SHORT_FICS_NAME[shorttype] except KeyError: # TODO: handle ICC wild types # print("key error in GAME_TYPES_BY_SHORT_FICS_NAME: %s" % shorttype) continue wplayer = self.connection.players.get(wname) bplayer = self.connection.players.get(bname) game = FICSGame(wplayer, bplayer, gameno=int(gameno), rated=(rated == "r"), private=(private == "p"), minutes=int(min), inc=int(inc), game_type=gametype) for player, rating in ((wplayer, wrating), (bplayer, brating)): if player.status != IC_STATUS_PLAYING: player.status = IC_STATUS_PLAYING if player.game != game: player.game = game rating = parseRating(rating) if player.ratings[gametype.rating_type] != rating: player.ratings[gametype.rating_type] = rating player.emit("ratings_changed", gametype.rating_type, player) if game not in previous_games: game = self.connection.games.get(game, emit=False) games.append(game) games_got.append(game) for game in previous_games: if game not in games_got and \ game not in self.connection.bm.gamesImObserving and \ game is not self.connection.bm.theGameImPlaying: self.connection.games.game_ended(game) game.wplayer.game = None game.bplayer.game = None self.connection.games.emit("FICSGameCreated", games)