Exemplo n.º 1
0
        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()
Exemplo n.º 2
0
 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)], ))
Exemplo n.º 3
0
    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
            ]
Exemplo n.º 4
0
    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
Exemplo n.º 5
0
    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
Exemplo n.º 6
0
    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
            ]
Exemplo n.º 7
0
    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
Exemplo n.º 8
0
    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"]
Exemplo n.º 9
0
 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))
Exemplo n.º 10
0
    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)
Exemplo n.º 11
0
    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