def analyse_moves(): should_black = conf.get("shouldBlack", True) should_white = conf.get("shouldWhite", True) from_current = conf.get("fromCurrent", True) start_ply = gmwidg.board.view.shown if from_current else 0 move_time = int(conf.get("max_analysis_spin", 3)) threshold = int(conf.get("variation_threshold_spin", 50)) for board in gamemodel.boards[start_ply:]: if stop_event.is_set(): break @idle_add def do(): gmwidg.board.view.setShownBoard(board) do() analyzer.setBoard(board) if threat_PV: inv_analyzer.setBoard(board) time.sleep(move_time + 0.1) ply = board.ply color = (ply - 1) % 2 if ply - 1 in gamemodel.scores and ply in gamemodel.scores and ( (color == BLACK and should_black) or (color == WHITE and should_white)): oldmoves, oldscore, olddepth = gamemodel.scores[ply - 1] oldscore = oldscore * -1 if color == BLACK else oldscore score_str = prettyPrintScore(oldscore, olddepth) moves, score, depth = gamemodel.scores[ply] score = score * -1 if color == WHITE else score diff = score - oldscore if (diff > threshold and color == BLACK) or (diff < -1 * threshold and color == WHITE): if threat_PV: try: if ply - 1 in gamemodel.spy_scores: oldmoves0, oldscore0, olddepth0 = gamemodel.spy_scores[ply - 1] score_str0 = prettyPrintScore(oldscore0, olddepth0) pv0 = listToMoves(gamemodel.boards[ply - 1], ["--"] + oldmoves0, validate=True) if len(pv0) > 2: gamemodel.add_variation(gamemodel.boards[ply - 1], pv0, comment="Treatening", score=score_str0) except ParsingError as e: # ParsingErrors may happen when parsing "old" lines from # analyzing engines, which haven't yet noticed their new tasks log.debug("__parseLine: Ignored (%s) from analyzer: ParsingError%s" % (' '.join(oldmoves), e)) try: pv = listToMoves(gamemodel.boards[ply - 1], oldmoves, validate=True) gamemodel.add_variation(gamemodel.boards[ply - 1], pv, comment="Better is", score=score_str) except ParsingError as e: # ParsingErrors may happen when parsing "old" lines from # analyzing engines, which haven't yet noticed their new tasks log.debug("__parseLine: Ignored (%s) from analyzer: ParsingError%s" % (' '.join(oldmoves), e)) widgets["analyze_game"].hide() widgets["analyze_ok_button"].set_sensitive(True) conf.set("analyzer_check", old_check_value) if threat_PV: conf.set("inv_analyzer_check", old_inv_check_value) message.dismiss()
def _testLine(self, engine, analyzer, board, analine, moves, score, depth): self.traceSignal(analyzer, 'analyze') engine.putline(analine) results = self.getSignalResults(analyzer) self.assertNotEqual(results, None, "signal wasn't sent") self.assertEqual(results, ([(listToMoves(board, moves), score, depth)], ))
def on_analyze(self, engine, analysis): if self.boardview.animating: return if self.boardview.model.isPlayingICSGame(): return if not self.active: return is_FAN = conf.get("figuresInNotation", False) for i, line in enumerate(analysis): if line is None: self.store[self.path + (i, )] = self.textOnlyRow("") continue board0 = self.engine.board board = board0.clone() movstrs, score, depth = line try: pv = listToMoves(board, movstrs, validate=True) except ParsingError as e: # ParsingErrors may happen when parsing "old" lines from # analyzing engines, which haven't yet noticed their new tasks log.debug( "__parseLine: Ignored (%s) from analyzer: ParsingError%s" % (' '.join(movstrs), e)) return except: return move = None if pv: move = pv[0] ply0 = board.ply if self.mode == HINT else board.ply + 1 counted_pv = [] for j, pvmove in enumerate(pv): ply = ply0 + j if ply % 2 == 0: mvcount = "%d." % (ply / 2 + 1) elif j == 0: mvcount = "%d..." % (ply / 2 + 1) else: mvcount = "" counted_pv.append("%s%s" % (mvcount, toFAN( board, pvmove) if is_FAN else toSAN(board, pvmove, True))) board = board.move(pvmove) goodness = (min(max(score, -250), 250) + 250) / 500.0 if self.engine.board.color == BLACK: score = -score self.store[self.path + (i, )] = [ (board0, move, pv), (prettyPrintScore(score, depth), 1, goodness), 0, False, " ".join(counted_pv), False, False ]
def _on_analyze(self, analyzer, analysis, analyzer_type): if self.board.view.animating: return if not self.menuitems[analyzer_type + "_mode"].active: return if len(analysis) >= 1 and analysis[0] is not None: ply, movstrs, score, depth, nps = analysis[0] board = analyzer.board try: moves = listToMoves(board, movstrs, validate=True) except ParsingError as e: # ParsingErrors may happen when parsing "old" lines from # analyzing engines, which haven't yet noticed their new tasks log.debug( "GameWidget._on_analyze(): Ignored (%s) from analyzer: ParsingError%s" % (' '.join(movstrs), e)) return if moves and (self.gamemodel.curplayer.__type__ == LOCAL or [ player.__type__ for player in self.gamemodel.players ] == [REMOTE, REMOTE] or self.gamemodel.status not in UNFINISHED_STATES): if moves[0].flag == DROP: piece = lmove.FCORD(moves[0].move) color = board.color if analyzer_type == HINT else 1 - board.color cord0 = board.getHoldingCord(color, piece) self._set_arrow(analyzer_type, (cord0, moves[0].cord1)) else: self._set_arrow(analyzer_type, moves[0].cords) else: self._set_arrow(analyzer_type, None) return False
def _on_analyze(self, analyzer, analysis, analyzer_type): if self.board.view.animating: return if not self.menuitems[analyzer_type + "_mode"].active: return if len(analysis) >= 1 and analysis[0] is not None: movstrs, score, depth = analysis[0] board = analyzer.board try: moves = listToMoves(board, movstrs, validate=True) except ParsingError as e: # ParsingErrors may happen when parsing "old" lines from # analyzing engines, which haven't yet noticed their new tasks log.debug("__parseLine: Ignored (%s) from analyzer: ParsingError%s" % (' '.join(movstrs), e)) return except: return if moves and (self.gamemodel.curplayer.__type__ == LOCAL or [player.__type__ for player in self.gamemodel.players] == [REMOTE, REMOTE] or self.gamemodel.status not in UNFINISHED_STATES): if moves[0].flag == DROP: piece = lmove.FCORD(moves[0].move) color = board.color if analyzer_type == HINT else 1 - board.color cord0 = board.getHoldingCord(color, piece) self._set_arrow(analyzer_type, (cord0, moves[0].cord1)) else: self._set_arrow(analyzer_type, moves[0].cords) else: self._set_arrow(analyzer_type, None) return False
def on_analyze(self, engine, analysis): if self.boardview.animating: return if self.boardview.model.isPlayingICSGame(): return if not self.active: return is_FAN = conf.get("figuresInNotation", False) for i, line in enumerate(analysis): if line is None: self.store[self.path + (i, )] = self.textOnlyRow("") continue board0 = self.engine.board board = board0.clone() movstrs, score, depth = line try: pv = listToMoves(board, movstrs, validate=True) except ParsingError as e: # ParsingErrors may happen when parsing "old" lines from # analyzing engines, which haven't yet noticed their new tasks log.debug("__parseLine: Ignored (%s) from analyzer: ParsingError%s" % (' '.join(movstrs), e)) return except: return move = None if pv: move = pv[0] ply0 = board.ply if self.mode == HINT else board.ply + 1 counted_pv = [] for j, pvmove in enumerate(pv): ply = ply0 + j if ply % 2 == 0: mvcount = "%d." % (ply / 2 + 1) elif j == 0: mvcount = "%d..." % (ply / 2 + 1) else: mvcount = "" counted_pv.append("%s%s" % (mvcount, toFAN(board, pvmove) if is_FAN else toSAN(board, pvmove, True))) board = board.move(pvmove) goodness = (min(max(score, -250), 250) + 250) / 500.0 if self.engine.board.color == BLACK: score = -score self.store[self.path + (i, )] = [ (board0, move, pv), (prettyPrintScore(score, depth), 1, goodness), 0, False, " ".join(counted_pv), False, False ]
def __parseLine (self, line): if not self.connected: return parts = line.split() if not parts: return #---------------------------------------------------------- Initializing if parts[0] == "id": self.ids[parts[1]] = " ".join(parts[2:]) if parts[1] == "name": self.setName(self.ids["name"]) return if parts[0] == "uciok": self.emit("readyForOptions") return if parts[0] == "readyok": self.emit("readyForMoves") return #------------------------------------------------------- Options parsing if parts[0] == "option": dic = {} last = 1 varlist = [] for i in range (2, len(parts)+1): if i == len(parts) or parts[i] in OPTKEYS: key = parts[last] value = " ".join(parts[last+1:i]) if "type" in dic and dic["type"] in TYPEDIC: value = TYPEDIC[dic["type"]](value) if key == "var": varlist.append(value) elif key == "type" and value == "string": dic[key] = "text" else: dic[key] = value last = i if varlist: dic["choices"] = varlist self.options[dic["name"]] = dic return #---------------------------------------------------------------- A Move if self.mode == NORMAL and parts[0] == "bestmove": with self.moveLock: self.needBestmove = False self.__sendQueuedGo() if self.ignoreNext: log.debug("__parseLine: line='%s' self.ignoreNext==True, returning" % \ line.strip(), extra={"task":self.defname}) self.ignoreNext = False self.readyForStop = True return if not self.waitingForMove: log.warning("__parseLine: self.waitingForMove==False, ignoring move=%s" % \ parts[1], extra={"task":self.defname}) self.pondermove = None return self.waitingForMove = False try: move = parseAny(self.board, parts[1]) except ParsingError as e: self.end(WHITEWON if self.board.color == BLACK else BLACKWON, WON_ADJUDICATION) return if not validate(self.board, move): # This is critical. To avoid game stalls, we need to resign on # behalf of the engine. log.error("__parseLine: move=%s didn't validate, putting 'del' in returnQueue. self.board=%s" % \ (repr(move), self.board), extra={"task":self.defname}) self.end(WHITEWON if self.board.color == BLACK else BLACKWON, WON_ADJUDICATION) return self._recordMove(self.board.move(move), move, self.board) log.debug("__parseLine: applied move=%s to self.board=%s" % \ (move, self.board), extra={"task":self.defname}) if self.ponderOn: self.pondermove = None # An engine may send an empty ponder line, simply to clear. if len(parts) == 4: # Engines don't always check for everything in their # ponders. Hence we need to validate. # But in some cases, what they send may not even be # correct AN - specially in the case of promotion. try: pondermove = parseAny(self.board, parts[3]) except ParsingError: pass else: if validate(self.board, pondermove): self.pondermove = pondermove self._startPonder() self.returnQueue.put(move) log.debug("__parseLine: put move=%s into self.returnQueue=%s" % \ (move, self.returnQueue.queue), extra={"task":self.defname}) return #----------------------------------------------------------- An Analysis if self.mode != NORMAL and parts[0] == "info" and "pv" in parts: multipv = 1 if "multipv" in parts: multipv = int(parts[parts.index("multipv")+1]) scoretype = parts[parts.index("score")+1] if scoretype in ('lowerbound', 'upperbound'): score = None else: score = int(parts[parts.index("score")+2]) if scoretype == 'mate': # print >> self.engine, "stop" if score != 0: sign = score/abs(score) score = sign*MATE_VALUE movstrs = parts[parts.index("pv")+1:] try: moves = listToMoves (self.board, movstrs, AN, validate=True, ignoreErrors=False) except ParsingError as e: # ParsingErrors may happen when parsing "old" lines from # analyzing engines, which haven't yet noticed their new tasks log.debug("__parseLine: Ignored (%s) from analyzer: ParsingError%s" % \ (' '.join(movstrs),e), extra={"task":self.defname}) return if "depth" in parts: depth = parts[parts.index("depth")+1] else: depth = "" if multipv <= len(self.analysis): self.analysis[multipv - 1] = (moves, score, depth) self.emit("analyze", self.analysis) return #----------------------------------------------- An Analyzer bestmove if self.mode != NORMAL and parts[0] == "bestmove": with self.moveLock: log.debug("__parseLine: processing analyzer bestmove='%s'" % \ line.strip(), extra={"task":self.defname}) self.needBestmove = False self.__sendQueuedGo(sendlast=True) return # Stockfish complaining it received a 'stop' without a corresponding 'position..go' if line.strip() == "Unknown command: stop": with self.moveLock: log.debug("__parseLine: processing '%s'" % line.strip(), extra={"task":self.defname}) self.ignoreNext = False self.needBestmove = False self.readyForStop = False self.__sendQueuedGo() return
def __parseLine (self, line): if line[0:1] == "#": # Debug line which we shall ignore as specified in CECPv2 specs return # log.debug("__parseLine: line=\"%s\"\n" % line.strip(), self.defname) parts = whitespaces.split(line.strip()) if parts[0] == "pong": self.lastpong = int(parts[1]) return # Illegal Move if parts[0].lower().find("illegal") >= 0: log.warn("__parseLine: illegal move: line=\"%s\", board=%s" \ % (line.strip(), self.board), self.defname) if parts[-2] == "sd" and parts[-1].isdigit(): print >> self.engine, "depth", parts[-1] return # A Move (Perhaps) if self.board: if parts[0] == "move": movestr = parts[1] # Old Variation elif d_plus_dot_expr.match(parts[0]) and parts[1] == "...": movestr = parts[2] else: movestr = False if movestr: log.debug("__parseLine: acquiring self.boardLock\n", self.defname) self.waitingForMove = False self.readyForMoveNowCommand = False self.boardLock.acquire() try: if self.engineIsInNotPlaying: # If engine was set in pause just before the engine sent its # move, we ignore it. However the engine has to know that we # ignored it, and thus we step it one back log.info("__parseLine: Discarding engine's move: %s\n" % movestr, self.defname) print >> self.engine, "undo" return else: try: move = parseAny(self.board, movestr) except ParsingError, e: self.end(WHITEWON if self.board.color == BLACK else BLACKWON, WON_ADJUDICATION) return if validate(self.board, move): self.board = None self.returnQueue.put(move) return self.end(WHITEWON if self.board.color == BLACK else BLACKWON, WON_ADJUDICATION) return finally: log.debug("__parseLine(): releasing self.boardLock\n", self.defname) self.boardLock.release() self.movecon.acquire() self.movecon.notifyAll() self.movecon.release() # Analyzing if self.engineIsInNotPlaying: if parts[:4] == ["0","0","0","0"]: # Crafty doesn't analyze until it is out of book print >> self.engine, "book off" return match = anare.match(line) if match: score, moves = match.groups() if "mat" in score.lower() or "#" in moves: # Will look either like -Mat 3 or Mat3 scoreval = MATE_VALUE if score.startswith('-'): scoreval = -scoreval else: scoreval = int(score) mvstrs = movere.findall(moves) try: moves = listToMoves (self.board, mvstrs, type=None, validate=True, ignoreErrors=False) except: # Errors may happen when parsing "old" lines from # analyzing engines, which haven't yet noticed their new tasks log.debug('Ignored an "old" line from analyzer: %s\n' % mvstrs, self.defname) return # Don't emit if we weren't able to parse moves, or if we have a move # to kill the opponent king - as it confuses many engines if moves and not self.board.board.opIsChecked(): #self.emit("analyze", [(moves, scoreval)]) return # Offers draw if parts[0:2] == ["offer", "draw"]: #self.emit("accept", Offer(DRAW_OFFER)) return # Resigns if parts[0] == "resign" or \ (parts[0] == "tellics" and parts[1] == "resign"): # buggy crafty # Previously: if "resign" in parts, # however, this is too generic, since "hint", "bk", # "feature option=.." and possibly other, future CECPv2 # commands can validly contain the word "resign" without this # being an intentional resign offer. #self.emit("offer", Offer(RESIGNATION)) return #if parts[0].lower() == "error": # return #Tell User Error if parts[0] == "tellusererror": # Create a non-modal non-blocking message dialog with the error: dlg = gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_CLOSE, message_format=None) # Use the engine name if already known, otherwise the defname: displayname = self.name if not displayname: displayname = self.defname # Compose the dialog text: dlg.set_markup(gobject.markup_escape_text(_("The engine %s reports an error:") % displayname) + "\n\n" + gobject.markup_escape_text(" ".join(parts[1:]))) # handle response signal so the "Close" button works: dlg.connect("response", lambda dlg, x: dlg.destroy()) dlg.show_all() return # Tell Somebody if parts[0][:4] == "tell" and \ parts[0][4:] in ("others", "all", "ics", "icsnoalias"): log.info("Ignoring tell %s: %s\n" % (parts[0][4:], " ".join(parts[1:]))) return if "feature" in parts: # Some engines send features after done=1, so we will iterate after done=1 too done1 = False # We skip parts before 'feature', as some engines give us lines like # White (1) : feature setboard=1 analyze...e="GNU Chess 5.07" done=1 parts = parts[parts.index("feature"):] for i, pair in enumerate(parts[1:]): # As "parts" is split with no thoughs on quotes or double quotes # we need to do some extra handling. if pair.find("=") < 0: continue key, value = pair.split("=",1) if value[0] in ('"',"'") and value[-1] in ('"',"'"): value = value[1:-1] # If our pair was unfinished, like myname="GNU, we search the # rest of the pairs for a quotating mark. elif value[0] in ('"',"'"): rest = value[1:] + " " + " ".join(parts[2+i:]) i = rest.find('"') j = rest.find("'") if i + j == -2: log.warn("Missing endquotation in %s feature", self.defname) value = rest elif min(i, j) != -1: value = rest[:min(i, j)] else: l = max(i, j) value = rest[:l] elif value.isdigit(): value = int(value) if key in self.supported_features: print >> self.engine, "accepted %s" % key else: print >> self.engine, "rejected %s" % key if key == "done": if value == 1: done1 = True continue elif value == 0: log.info("Adds %d seconds timeout\n" % TIME_OUT_SECOND, self.defname) # This'll buy you some more time self.timeout = time.time()+TIME_OUT_SECOND self.returnQueue.put("not ready") return if key == "smp" and value == 1: self.options["cores"] = {"name": "cores", "type": "spin", "default": 1, "min": 1, "max": 64} elif key == "memory" and value == 1: self.options["memory"] = {"name": "memory", "type": "spin", "default": 32, "min": 1, "max": 4096} elif key == "option" and key != "done": option = self.__parse_option(value) self.options[option["name"]] = option else: self.features[key] = value if key == "myname" and not self.name: self.setName(value) if done1: # Start a new game before using the engine: # (CECPv2 engines) print >> self.engine, "new" # We are now ready for play: #self.emit("readyForOptions") #self.emit("readyForMoves") self.returnQueue.put("ready") # A hack to get better names in protover 1. # Unfortunately it wont work for now, as we don't read any lines from # protover 1 engines. When should we stop? if self.protover == 1: if self.defname[0] in ''.join(parts): basis = self.defname[0] name = ' '.join(itertools.dropwhile(lambda part: basis not in part, parts)) self.features['myname'] = name if not self.name: self.setName(name) def __parse_option(self, option): if " -check " in option: name, value = option.split(" -check ") return {"type": "check", "name": name, "default": bool(int(value))} elif " -spin " in option: name, value = option.split(" -spin ") defv, minv, maxv = value.split() return {"type": "spin", "name": name, "default": int(defv), "min": int(minv), "max": int(maxv)} elif " -slider " in option: name, value = option.split(" -slider ") defv, minv, maxv = value.split() return {"type": "spin", "name": name, "default": int(defv), "min": int(minv), "max": int(maxv)} elif " -string " in option: name, value = option.split(" -string ") return {"type": "text", "name": name, "default": value} elif " -file " in option: name, value = option.split(" -file ") return {"type": "text", "name": name, "default": value} elif " -path " in option: name, value = option.split(" -path ") return {"type": "text", "name": name, "default": value} elif " -combo " in option: name, value = option.split(" -combo ") return {"type": "combo", "name": name, "default": value} elif " -button" in option: pos = option.find(" -button") return {"type": "button", "name": option[:pos]} elif " -save" in option: pos = option.find(" -save") return {"type": "button", "name": option[:pos]} elif " -reset" in option: pos = option.find(" -reset") return {"type": "button", "name": option[:pos]} #=========================================================================== # Info #=========================================================================== def canAnalyze (self): assert self.ready, "Still waiting for done=1" return self.features["analyze"] def maxAnalysisLines (self): return 1 def requestMultiPV (self, setting): return 1 def isAnalyzing (self): return self.mode in (ANALYZING, INVERSE_ANALYZING) def __repr__ (self): if self.name: return self.name return self.features["myname"]
def _testLine(self, engine, analyzer, board, analine, moves, score): self.traceSignal(analyzer, 'analyze') engine.putline(analine) results = self.getSignalResults(analyzer) self.assertNotEqual(results, None, "signal wasn't sent") self.assertEqual(results, (listToMoves(board,moves), score))
def __parseLine(self, line): if line[0:1] == "#": # Debug line which we shall ignore as specified in CECPv2 specs return # log.debug("__parseLine: line=\"%s\"" % line.strip(), extra={"task":self.defname}) parts = whitespaces.split(line.strip()) if parts[0] == "pong": self.lastpong = int(parts[1]) return # Illegal Move if parts[0].lower().find("illegal") >= 0: log.warning("__parseLine: illegal move: line=\"%s\", board=%s" \ % (line.strip(), self.board), extra={"task":self.defname}) if parts[-2] == "sd" and parts[-1].isdigit(): print("depth", parts[-1], file=self.engine) return # A Move (Perhaps) if self.board: if parts[0] == "move": movestr = parts[1] # Old Variation elif d_plus_dot_expr.match(parts[0]) and parts[1] == "...": movestr = parts[2] else: movestr = False if movestr: log.debug("__parseLine: acquiring self.boardLock", extra={"task": self.defname}) self.waitingForMove = False self.readyForMoveNowCommand = False self.boardLock.acquire() try: if self.engineIsInNotPlaying: # If engine was set in pause just before the engine sent its # move, we ignore it. However the engine has to know that we # ignored it, and thus we step it one back log.info("__parseLine: Discarding engine's move: %s" % movestr, extra={"task": self.defname}) print("undo", file=self.engine) return else: try: move = parseAny(self.board, movestr) except ParsingError as e: self.end( WHITEWON if self.board.color == BLACK else BLACKWON, WON_ADJUDICATION) return if validate(self.board, move): self.board = None self.returnQueue.put(move) return self.end( WHITEWON if self.board.color == BLACK else BLACKWON, WON_ADJUDICATION) return finally: log.debug("__parseLine(): releasing self.boardLock", extra={"task": self.defname}) self.boardLock.release() self.movecon.acquire() self.movecon.notifyAll() self.movecon.release() # Analyzing if self.engineIsInNotPlaying: if parts[:4] == ["0", "0", "0", "0"]: # Crafty doesn't analyze until it is out of book print("book off", file=self.engine) return match = anare.match(line) if match: depth, score, moves = match.groups() if "mat" in score.lower() or "#" in moves: # Will look either like -Mat 3 or Mat3 scoreval = MATE_VALUE if score.startswith('-'): scoreval = -scoreval else: scoreval = int(score) mvstrs = movere.findall(moves) try: moves = listToMoves(self.board, mvstrs, type=None, validate=True, ignoreErrors=False) except: # Errors may happen when parsing "old" lines from # analyzing engines, which haven't yet noticed their new tasks log.debug('Ignored an "old" line from analyzer: %s %s' % (self.board, mvstrs), extra={"task": self.defname}) return # Don't emit if we weren't able to parse moves, or if we have a move # to kill the opponent king - as it confuses many engines if moves and not self.board.board.opIsChecked(): self.emit("analyze", [(moves, scoreval, depth.strip())]) return # Offers draw if parts[0:2] == ["offer", "draw"]: self.emit("accept", Offer(DRAW_OFFER)) return # Resigns if parts[0] == "resign" or \ (parts[0] == "tellics" and parts[1] == "resign"): # buggy crafty # Previously: if "resign" in parts, # however, this is too generic, since "hint", "bk", # "feature option=.." and possibly other, future CECPv2 # commands can validly contain the word "resign" without this # being an intentional resign offer. self.emit("offer", Offer(RESIGNATION)) return #if parts[0].lower() == "error": # return #Tell User Error if parts[0] == "tellusererror": # We don't want to see our stop analyzer hack as an error message if "8/8/8/8/8/8/8/8" in "".join(parts[1:]): return # Create a non-modal non-blocking message dialog with the error: dlg = Gtk.MessageDialog(parent=None, flags=0, type=Gtk.MessageType.WARNING, buttons=Gtk.ButtonsType.CLOSE, message_format=None) # Use the engine name if already known, otherwise the defname: displayname = self.name if not displayname: displayname = self.defname # Compose the dialog text: dlg.set_markup( GObject.markup_escape_text( _("The engine %s reports an error:") % displayname) + "\n\n" + GObject.markup_escape_text(" ".join(parts[1:]))) # handle response signal so the "Close" button works: dlg.connect("response", lambda dlg, x: dlg.destroy()) dlg.show_all() return # Tell Somebody if parts[0][:4] == "tell" and \ parts[0][4:] in ("others", "all", "ics", "icsnoalias"): log.info("Ignoring tell %s: %s" % (parts[0][4:], " ".join(parts[1:]))) return if "feature" in parts: # Some engines send features after done=1, so we will iterate after done=1 too done1 = False # We skip parts before 'feature', as some engines give us lines like # White (1) : feature setboard=1 analyze...e="GNU Chess 5.07" done=1 parts = parts[parts.index("feature"):] for i, pair in enumerate(parts[1:]): # As "parts" is split with no thoughs on quotes or double quotes # we need to do some extra handling. if pair.find("=") < 0: continue key, value = pair.split("=", 1) if not key in self.features: continue if value.startswith('"') and value.endswith('"'): value = value[1:-1] # If our pair was unfinished, like myname="GNU, we search the # rest of the pairs for a quotating mark. elif value[0] == '"': rest = value[1:] + " " + " ".join(parts[2 + i:]) j = rest.find('"') if j == -1: log.warning("Missing endquotation in %s feature", extra={"task": self.defname}) value = rest else: value = rest[:j] elif value.isdigit(): value = int(value) if key in self.supported_features: print("accepted %s" % key, file=self.engine) else: print("rejected %s" % key, file=self.engine) if key == "done": if value == 1: done1 = True continue elif value == 0: log.info("Adds %d seconds timeout" % TIME_OUT_SECOND, extra={"task": self.defname}) # This'll buy you some more time self.timeout = time.time() + TIME_OUT_SECOND self.returnQueue.put("not ready") return if key == "smp" and value == 1: self.options["cores"] = { "name": "cores", "type": "spin", "default": 1, "min": 1, "max": 64 } elif key == "memory" and value == 1: self.options["memory"] = { "name": "memory", "type": "spin", "default": 32, "min": 1, "max": 4096 } elif key == "option" and key != "done": option = self.__parse_option(value) self.options[option["name"]] = option else: self.features[key] = value if key == "myname" and not self.name: self.setName(value) if done1: # Start a new game before using the engine: # (CECPv2 engines) print("new", file=self.engine) # We are now ready for play: self.emit("readyForOptions") self.emit("readyForMoves") self.returnQueue.put("ready") # A hack to get better names in protover 1. # Unfortunately it wont work for now, as we don't read any lines from # protover 1 engines. When should we stop? if self.protover == 1: if self.defname[0] in ''.join(parts): basis = self.defname[0] name = ' '.join( itertools.dropwhile(lambda part: basis not in part, parts)) self.features['myname'] = name if not self.name: self.setName(name)
def __parseLine(self, line): if not self.connected: return parts = line.split() if not parts: return #---------------------------------------------------------- Initializing if parts[0] == "id": self.ids[parts[1]] = " ".join(parts[2:]) if parts[1] == "name": self.setName(self.ids["name"]) return if parts[0] == "uciok": self.emit("readyForOptions") return if parts[0] == "readyok": self.emit("readyForMoves") return #------------------------------------------------------- Options parsing if parts[0] == "option": dic = {} last = 1 varlist = [] for i in range(2, len(parts) + 1): if i == len(parts) or parts[i] in OPTKEYS: key = parts[last] value = " ".join(parts[last + 1:i]) if "type" in dic and dic["type"] in TYPEDIC: value = TYPEDIC[dic["type"]](value) if key == "var": varlist.append(value) elif key == "type" and value == "string": dic[key] = "text" else: dic[key] = value last = i if varlist: dic["choices"] = varlist self.options[dic["name"]] = dic return #---------------------------------------------------------------- A Move if self.mode == NORMAL and parts[0] == "bestmove": with self.moveLock: self.needBestmove = False self.__sendQueuedGo() if self.ignoreNext: log.debug("__parseLine: line='%s' self.ignoreNext==True, returning" % \ line.strip(), extra={"task":self.defname}) self.ignoreNext = False self.readyForStop = True return if not self.waitingForMove: log.warning("__parseLine: self.waitingForMove==False, ignoring move=%s" % \ parts[1], extra={"task":self.defname}) self.pondermove = None return self.waitingForMove = False try: move = parseAny(self.board, parts[1]) except ParsingError as e: self.end( WHITEWON if self.board.color == BLACK else BLACKWON, WON_ADJUDICATION) return if not validate(self.board, move): # This is critical. To avoid game stalls, we need to resign on # behalf of the engine. log.error("__parseLine: move=%s didn't validate, putting 'del' in returnQueue. self.board=%s" % \ (repr(move), self.board), extra={"task":self.defname}) self.end( WHITEWON if self.board.color == BLACK else BLACKWON, WON_ADJUDICATION) return self._recordMove(self.board.move(move), move, self.board) log.debug("__parseLine: applied move=%s to self.board=%s" % \ (move, self.board), extra={"task":self.defname}) if self.ponderOn: self.pondermove = None # An engine may send an empty ponder line, simply to clear. if len(parts) == 4: # Engines don't always check for everything in their # ponders. Hence we need to validate. # But in some cases, what they send may not even be # correct AN - specially in the case of promotion. try: pondermove = parseAny(self.board, parts[3]) except ParsingError: pass else: if validate(self.board, pondermove): self.pondermove = pondermove self._startPonder() self.returnQueue.put(move) log.debug("__parseLine: put move=%s into self.returnQueue=%s" % \ (move, self.returnQueue.queue), extra={"task":self.defname}) return #----------------------------------------------------------- An Analysis if self.mode != NORMAL and parts[0] == "info" and "pv" in parts: multipv = 1 if "multipv" in parts: multipv = int(parts[parts.index("multipv") + 1]) scoretype = parts[parts.index("score") + 1] if scoretype in ('lowerbound', 'upperbound'): score = None else: score = int(parts[parts.index("score") + 2]) if scoretype == 'mate': # print >> self.engine, "stop" if score != 0: sign = score / abs(score) score = sign * MATE_VALUE movstrs = parts[parts.index("pv") + 1:] try: moves = listToMoves(self.board, movstrs, AN, validate=True, ignoreErrors=False) except ParsingError as e: # ParsingErrors may happen when parsing "old" lines from # analyzing engines, which haven't yet noticed their new tasks log.debug("__parseLine: Ignored (%s) from analyzer: ParsingError%s" % \ (' '.join(movstrs),e), extra={"task":self.defname}) return if "depth" in parts: depth = parts[parts.index("depth") + 1] else: depth = "" if multipv <= len(self.analysis): self.analysis[multipv - 1] = (moves, score, depth) self.emit("analyze", self.analysis) return #----------------------------------------------- An Analyzer bestmove if self.mode != NORMAL and parts[0] == "bestmove": with self.moveLock: log.debug("__parseLine: processing analyzer bestmove='%s'" % \ line.strip(), extra={"task":self.defname}) self.needBestmove = False self.__sendQueuedGo(sendlast=True) return # Stockfish complaining it received a 'stop' without a corresponding 'position..go' if line.strip() == "Unknown command: stop": with self.moveLock: log.debug("__parseLine: processing '%s'" % line.strip(), extra={"task": self.defname}) self.ignoreNext = False self.needBestmove = False self.readyForStop = False self.__sendQueuedGo() return