Example #1
0
    def __init__(self, timemodel=None, variant=NormalChess):
        GObject.GObject.__init__(self)
        Thread.__init__(self, name=fident(self.run))
        self.daemon = True
        self.variant = variant
        self.boards = [variant.board(setup=True)]

        self.moves = []
        self.scores = {}
        self.players = []

        self.gameno = None
        self.variations = [self.boards]

        self.status = WAITING_TO_START
        self.reason = UNKNOWN_REASON

        if timemodel is None:
            self.timemodel = TimeModel()
        else:
            self.timemodel = timemodel

        self.connections = defaultdict(list)  # mainly for IC subclasses

        now = datetime.datetime.now()
        self.tags = {
            "Event": _("Local Event"),
            "Site": _("Local Site"),
            "Round": 1,
            "Year": now.year,
            "Month": now.month,
            "Day": now.day,
            "Time": "%02d:%02d:00" % (now.hour, now.minute),
            "Result": "*",
        }

        self.endstatus = None
        self.timed = self.timemodel.secs != 0 or self.timemodel.gain != 0

        if self.timed:
            self.tags["TimeControl"] = \
                "%d+%d" % (self.timemodel.minutes*60, self.timemodel.gain)
            # Notice: tags["WhiteClock"] and tags["BlackClock"] are never set
            # on the gamemodel, but simply written or read during saving/
            # loading from pgn. If you want to know the time left for a player,
            # check the time model.

        # Keeps track of offers, so that accepts can be spotted
        self.offers = {}
        # True if the game has been changed since last save
        self.needsSave = False
        # The uri the current game was loaded from, or None if not a loaded game
        self.uri = None

        self.spectators = {}

        self.applyingMoveLock = RLock()
        self.undoLock = RLock()
        self.undoQueue = Queue()
Example #2
0
    def startClicked(self, button):
        color = self.widgets["colorDock"].get_child().get_active()
        if color == 2:
            color = random.choice([WHITE, BLACK])

        opp = self.widgets["opponentDock"].get_child()
        tree_iter = opp.get_active_iter()
        if tree_iter is not None:
            model = opp.get_model()
            engine = model[tree_iter][1]

        opponent = self.widgets["opponentDock"].get_child().get_active()
        difficulty = int(self.widgets["skillSlider"].get_value())

        gamemodel = GameModel(TimeModel(5 * 60, 0))

        name = conf.get("firstName")
        player0tup = (LOCAL, Human, (color, name), name)
        if opponent == 0:
            name = conf.get("secondName")
            player1tup = (LOCAL, Human, (1 - color, name), name)
        else:
            engine = discoverer.getEngineByName(engine)
            name = discoverer.getName(engine)
            player1tup = (ARTIFICIAL, discoverer.initPlayerEngine,
                          (engine, 1 - color, difficulty,
                           variants[NORMALCHESS], 5 * 60, 0), name)

        perspective = perspective_manager.get_perspective("games")
        if color == WHITE:
            asyncio. async (perspective.generalStart(gamemodel, player0tup,
                                                     player1tup))
        else:
            asyncio. async (perspective.generalStart(gamemodel, player1tup,
                                                     player0tup))
Example #3
0
def start_puzzle_from(filename, index=None):
    if filename.lower().endswith(".pgn"):
        if filename.startswith("lichess_study"):
            chessfile = PGNFile(protoopen(addDataPrefix("learn/puzzles/%s" % filename), encoding="utf-8"))
        else:
            chessfile = PGNFile(protoopen(addDataPrefix("learn/puzzles/%s" % filename)))
        chessfile.limit = 1000
        chessfile.init_tag_database()
    elif filename.lower().endswith(".olv"):
        chessfile = OLVFile(protoopen(addDataPrefix("learn/puzzles/%s" % filename), encoding="utf-8"))

    records, plys = chessfile.get_records()

    progress = puzzles_solving_progress.get(filename, [0] * chessfile.count)

    if index is None:
        index = progress.index(0)

    rec = records[index]

    timemodel = TimeModel(0, 0)
    gamemodel = LearnModel(timemodel)

    chessfile.loadToModel(rec, 0, gamemodel)

    start_puzzle_game(gamemodel, filename, records, index, rec)
Example #4
0
def start_endgame_from(pieces):
    fen = create_fen(pieces)

    timemodel = TimeModel(0, 0)
    gamemodel = LearnModel(timemodel)
    gamemodel.set_learn_data(ENDGAME, pieces)

    player_name = conf.get("firstName", _("You"))
    p0 = (LOCAL, Human, (WHITE, player_name), player_name)

    engine = discoverer.getEngineByName(discoverer.getEngineLearn())
    ponder_off = True
    engine_name = discoverer.getName(engine)
    p1 = (ARTIFICIAL, discoverer.initPlayerEngine,
          (engine, BLACK, 20, variants[NORMALCHESS], 60, 0, 0,
           ponder_off), engine_name)

    def restart_analyzer(gamemodel):
        asyncio. async (gamemodel.restart_analyzer(HINT))

    gamemodel.connect("learn_success", restart_analyzer)

    def on_game_started(gamemodel):
        perspective.activate_panel("annotationPanel")
        asyncio. async (gamemodel.start_analyzer(
            HINT, force_engine=discoverer.getEngineLearn()))

    gamemodel.connect("game_started", on_game_started)

    perspective = perspective_manager.get_perspective("games")
    asyncio. async (perspective.generalStart(gamemodel,
                                             p0,
                                             p1,
                                             loaddata=(StringIO(fen),
                                                       fen_loader, 0, -1)))
Example #5
0
def createRematch(gamemodel):
    """ If gamemodel contains only LOCAL or ARTIFICIAL players, this starts a
        new game, based on the info in gamemodel """

    if gamemodel.timed:
        secs = gamemodel.timemodel.intervals[0][WHITE]
        gain = gamemodel.timemodel.gain
    else:
        secs = 0
        gain = 0
    newgamemodel = GameModel(TimeModel(secs, gain), variant=gamemodel.variant)

    wp = gamemodel.players[WHITE]
    bp = gamemodel.players[BLACK]

    if wp.__type__ == LOCAL:
        player1tup = (wp.__type__, wp.__class__, (BLACK, repr(wp)), repr(wp))
        if bp.__type__ == LOCAL:
            player0tup = (bp.__type__, bp.__class__, (WHITE, repr(wp)),
                          repr(bp))
        else:
            engine = discoverer.getEngineByMd5(bp.md5)
            player0tup = (ARTIFICIAL, discoverer.initPlayerEngine,
                          (engine, WHITE, bp.strength, gamemodel.variant, secs,
                           gain), repr(bp))
    else:
        player0tup = (bp.__type__, bp.__class__, (WHITE, repr(bp)), repr(bp))
        engine = discoverer.getEngineByMd5(wp.md5)
        player1tup = (ARTIFICIAL, discoverer.initPlayerEngine,
                      (engine, BLACK, wp.strength, gamemodel.variant, secs,
                       gain), repr(wp))

    ionest.generalStart(newgamemodel, player0tup, player1tup)
Example #6
0
    def onGamePreview(self, adm, ficsgame):
        log.debug("Archived panel onGamePreview: %s" % ficsgame)

        timemodel = TimeModel(ficsgame.minutes * 60, ficsgame.inc)

        gamemodel = ICGameModel(self.connection, ficsgame, timemodel)

        # The players need to start listening for moves IN this method if they
        # want to be noticed of all moves the FICS server sends us from now on.
        # Hence the lazy loading is skipped.
        wplayer, bplayer = ficsgame.wplayer, ficsgame.bplayer
        player0tup = (REMOTE, ICPlayer,
                      (gamemodel, wplayer.name, -1, WHITE, wplayer.long_name(),
                       wplayer.getRatingByGameType(ficsgame.game_type)),
                      wplayer.long_name())
        player1tup = (REMOTE, ICPlayer,
                      (gamemodel, bplayer.name, -1, BLACK, bplayer.long_name(),
                       bplayer.getRatingByGameType(ficsgame.game_type)),
                      bplayer.long_name())

        perspective = perspective_manager.get_perspective("games")
        create_task(
            perspective.generalStart(
                gamemodel, player0tup, player1tup,
                (StringIO(ficsgame.board.pgn), pgn, 0, -1)))
        gamemodel.connect("game_started", self.on_game_start, ficsgame)
Example #7
0
def start_lesson_from(filename, index=None):
    chessfile = PGNFile(protoopen(addDataPrefix("learn/lessons/%s" %
                                                filename)))
    chessfile.limit = 1000
    chessfile.init_tag_database()
    records, plys = chessfile.get_records()

    progress = lessons_solving_progress.get(filename, [0] * chessfile.count)

    if index is None:
        index = progress.index(0)

    rec = records[index]

    timemodel = TimeModel(0, 0)
    gamemodel = LearnModel(timemodel)

    chessfile.loadToModel(rec, -1, gamemodel)

    if len(gamemodel.moves) > 0:
        start_lesson_game(gamemodel, filename, chessfile, records, index, rec)
    else:
        start_puzzle_game(gamemodel,
                          filename,
                          records,
                          index,
                          rec,
                          from_lesson=True)
    def row_activated(self, widget, path, col):
        if path is None:
            return
        pieces = ENDGAMES[path[0]][0]

        fen = self.create_fen(pieces)

        timemodel = TimeModel(0, 0)
        gamemodel = GameModel(timemodel)
        gamemodel.set_practice_game()

        name = conf.get("firstName", _("You"))
        p0 = (LOCAL, Human, (WHITE, name), name)

        engine = discoverer.getEngineByName(stockfish_name)
        name = discoverer.getName(engine)
        p1 = (ARTIFICIAL, discoverer.initPlayerEngine,
              (engine, BLACK, 20, variants[NORMALCHESS], 60, 0, 0, True), name)

        perspective = perspective_manager.get_perspective("games")
        asyncio. async (perspective.generalStart(gamemodel,
                                                 p0,
                                                 p1,
                                                 loaddata=(StringIO(fen),
                                                           fen_loader, 0, -1)))
Example #9
0
def runGame():
    a, b = findMatch()
    if a == None:
        print("All games have now been played. Here are the final scores:")
        printResults()
        mainloop.quit()
        return
    current[0] = a
    current[1] = b

    game = GameModel(TimeModel(minutes * 60, 0))
    game.connect('game_started', cb_gamestarted)
    game.connect('game_ended', cb_gameended)
    p0 = discoverer.initPlayerEngine(engines[a],
                                     WHITE,
                                     8,
                                     variants[NORMALCHESS],
                                     secs=minutes * 60,
                                     incr=0,
                                     forcePonderOff=True)
    p1 = discoverer.initPlayerEngine(engines[b],
                                     BLACK,
                                     8,
                                     variants[NORMALCHESS],
                                     secs=minutes * 60,
                                     incr=0,
                                     forcePonderOff=True)
    game.setPlayers([p0, p1])
    game.start()
Example #10
0
        def coro():
            gamemodel = GameModel(TimeModel(1, 0))

            player0tup = (LOCAL, Human, (WHITE, "w"), "w")
            player1tup = (LOCAL, Human, (BLACK, "b"), "b")

            def on_game_end(game, state, event):
                event.set()

            event = asyncio.Event()
            gamemodel.connect("game_ended", on_game_end, event)

            def on_players_changed(game):
                # fill fools mate moves to players move queue
                p0 = game.players[0]
                p0.move_queue.put_nowait(Move(newMove(F2, F3)))
                p0.move_queue.put_nowait(Move(newMove(G2, G4)))

                p1 = gamemodel.players[1]
                p1.move_queue.put_nowait(Move(newMove(E7, E5)))
                p1.move_queue.put_nowait(Move(newMove(D8, H4)))

            gamemodel.connect("players_changed", on_players_changed)

            asyncio. async (self.games_persp.generalStart(
                gamemodel, player0tup, player1tup))

            # waiting for game end ...
            yield from event.wait()

            fen = "rnb1kbnr/pppp1ppp/8/4p3/6Pq/5P2/PPPPP2P/RNBQKBNR w KQkq - 1 3"
            self.assertEqual(gamemodel.boards[-1].board.asFen(), fen)

            # Now save our game to pychess.pgn
            self.games_persp.saveGamePGN(gamemodel)
Example #11
0
    def row_activated(self, widget, path, col):
        if path is None:
            return
        filename = addDataPrefix("lectures/%s" % LESSONS[path[0]][0])

        chessfile = PGNFile(protoopen(filename))
        self.importer = PgnImport(chessfile)
        chessfile.init_tag_database(self.importer)
        records, plys = chessfile.get_records()

        rec = records[random.randrange(0, len(records))]
        print(rec)

        timemodel = TimeModel(0, 0)
        gamemodel = GameModel(timemodel)
        gamemodel.set_lesson_game()

        chessfile.loadToModel(rec, -1, gamemodel)

        name = conf.get("firstName", _("You"))
        p0 = (LOCAL, Human, (WHITE, name), name)
        name = "pychessbot"
        p1 = (LOCAL, Human, (BLACK, name), name)

        gamemodel.status = WAITING_TO_START
        perspective = perspective_manager.get_perspective("games")
        asyncio. async (perspective.generalStart(gamemodel, p0, p1))
Example #12
0
    def startClicked(self, button):
        color = self.widgets["colorDock"].get_child().active
        if color == 2:
            color = random.choice([WHITE, BLACK])
        opponent = self.widgets["opponentDock"].get_child().active
        difficulty = int(self.widgets["skillSlider"].get_value())

        gamemodel = GameModel(TimeModel(5 * 60, 0))

        name = conf.get("firstName", _("You"))
        player0tup = (LOCAL, Human, (color, name), name)
        if opponent == 0:
            name = conf.get("secondName", _("Guest"))
            player1tup = (LOCAL, Human, (1 - color, name), name)
        else:
            engine = discoverer.getEngineN(opponent - 1)
            name = discoverer.getName(engine)
            player1tup = (ARTIFICIAL, discoverer.initPlayerEngine,
                          (engine, 1 - color, difficulty,
                           variants[NORMALCHESS], 5 * 60, 0), name)

        if color == WHITE:
            game_handler.generalStart(gamemodel, player0tup, player1tup)
        else:
            game_handler.generalStart(gamemodel, player1tup, player0tup)
Example #13
0
            def coro(variant):
                self.game = GameModel(TimeModel(60, 0), variant)
                self.game.setPlayers([self.p0, self.p1])

                def on_game_end(game, state, event):
                    event.set()

                event = asyncio.Event()
                self.game.connect("game_ended", on_game_end, event)

                self.p0.prestart()
                self.p1.prestart()

                if self.game.variant.need_initial_board:
                    for player in self.game.players:
                        player.setOptionInitialBoard(self.game)

                print(variant.name)
                self.game.start()

                yield from event.wait()

                pgn = StringIO()
                print(save(pgn, self.game))

                self.assertIsNone(self.p0.invalid_move)
                self.assertIsNone(self.p1.invalid_move)
Example #14
0
    def __init__ (self, timemodel=None, variant=NormalChess):        
        GObject.GObject.__init__(self)
        Thread.__init__(self, name=fident(self.run))
        self.daemon = True
        self.variant = variant
        self.boards = [variant.board(setup=True)]
        
        self.moves = []
        self.scores = {}
        self.players = []
        
        self.gameno = None
        self.variations = [self.boards]
        
        self.status = WAITING_TO_START
        self.reason = UNKNOWN_REASON
        
        if timemodel is None:
            self.timemodel = TimeModel()
        else:
            self.timemodel = timemodel
        
        self.connections = defaultdict(list)  # mainly for IC subclasses
        
        now = datetime.datetime.now()
        self.tags = {
            "Event": _("Local Event"),
            "Site":  _("Local Site"),
            "Round": 1,
            "Year":  now.year,
            "Month": now.month,
            "Day":   now.day,
            "Time":  "%02d:%02d:00" % (now.hour, now.minute),
            "Result": "*",
        }

        self.endstatus = None
        self.timed = self.timemodel.secs!=0 or self.timemodel.gain!=0

        if self.timed:
            self.tags["TimeControl"] = \
                "%d+%d" % (self.timemodel.minutes*60, self.timemodel.gain)
            # Notice: tags["WhiteClock"] and tags["BlackClock"] are never set
            # on the gamemodel, but simply written or read during saving/
            # loading from pgn. If you want to know the time left for a player,
            # check the time model.
        
        # Keeps track of offers, so that accepts can be spotted
        self.offers = {}
        # True if the game has been changed since last save
        self.needsSave = False
        # The uri the current game was loaded from, or None if not a loaded game
        self.uri = None
        
        self.spectators = {}
        
        self.applyingMoveLock = RLock()
        self.undoLock = RLock()
        self.undoQueue = Queue()
    def onObserveGameCreated(self, bm, ficsgame):
        log.debug("FICS.onObserveGameCreated: %s" % ficsgame)

        timemodel = TimeModel(ficsgame.minutes * 60, ficsgame.inc)

        gamemodel = ICGameModel(self.connection, ficsgame, timemodel)
        gamemodel.connect("game_started", self.onGameModelStarted, ficsgame)

        # The players need to start listening for moves IN this method if they
        # want to be noticed of all moves the FICS server sends us from now on
        wplayer, bplayer = ficsgame.wplayer, ficsgame.bplayer

        player0tup = (
            REMOTE,
            ICPlayer,
            (
                gamemodel,
                wplayer.name,
                ficsgame.gameno,
                WHITE,
                wplayer.long_name(),
                wplayer.getRatingForCurrentGame(),
            ),
            wplayer.long_name(),
        )
        player1tup = (
            REMOTE,
            ICPlayer,
            (
                gamemodel,
                bplayer.name,
                ficsgame.gameno,
                BLACK,
                bplayer.long_name(),
                bplayer.getRatingForCurrentGame(),
            ),
            bplayer.long_name(),
        )

        perspective = perspective_manager.get_perspective("games")
        create_task(
            perspective.generalStart(
                gamemodel,
                player0tup,
                player1tup,
                (StringIO(ficsgame.board.pgn), pgn, 0, -1),
            ))

        if ficsgame.relation == IC_POS_OBSERVING_EXAMINATION:
            if 1:  # int(self.connection.lvm.variablesBackup["kibitz"]) == 0:
                self.connection.cm.whisper(
                    _("You have to set kibitz on to see bot messages here."))
            self.connection.fm.finger(bplayer.name)
            self.connection.fm.finger(wplayer.name)
        elif ficsgame.relation == IC_POS_EXAMINATING:
            gamemodel.examined = True
        if not self.connection.ICC:
            allob = "allob " + str(ficsgame.gameno)
            gamemodel.connection.client.run_command(allob)
Example #16
0
def do(discoverer):
    game = GameModel(TimeModel(60,0))
    #game.connect('game_started', cb_gamestarted2)
    game.connect('game_ended', lambda *a: mainloop.quit())
    p0 = discoverer.initPlayerEngine(discoverer.getEngines()['rybka'], WHITE, 7, variants[NORMALCHESS], 60)
    p1 = discoverer.initPlayerEngine(discoverer.getEngines()['gnuchess'], BLACK, 7, variants[NORMALCHESS], 60)
    game.setPlayers([p0,p1])
    game.start()
Example #17
0
        def onResponse(dialog, res):
            cls.widgets["newgamedialog"].hide()
            cls.widgets["newgamedialog"].disconnect(handlerId)
            if res != gtk.RESPONSE_OK:
                return

            # Find variant
            if cls.widgets["playNormalRadio"].get_active():
                variant_index = NORMALCHESS
            elif cls.widgets["playVariant1Radio"].get_active():
                variant_index = conf.get("ngvariant1", FISCHERRANDOMCHESS)
            else:
                variant_index = conf.get("ngvariant2", LOSERSCHESS)
            variant = variants[variant_index]

            # Find time
            if cls.widgets["notimeRadio"].get_active():
                secs = 0
                incr = 0
            elif cls.widgets["blitzRadio"].get_active():
                secs = cls.widgets["ngblitz min"].get_value_as_int()*60
                incr = cls.widgets["ngblitz gain"].get_value_as_int()
            elif cls.widgets["rapidRadio"].get_active():
                secs = cls.widgets["ngrapid min"].get_value_as_int()*60
                incr = cls.widgets["ngrapid gain"].get_value_as_int()
            elif cls.widgets["normalRadio"].get_active():
                secs = cls.widgets["ngnormal min"].get_value_as_int()*60
                incr = cls.widgets["ngnormal gain"].get_value_as_int()

            # Find players
            player0 = cls.widgets["whitePlayerCombobox"].get_active()
            player0 = playerItems[0].index(playerItems[variant_index][player0])
            diffi0 = int(cls.widgets["skillSlider1"].get_value())
            player1 = cls.widgets["blackPlayerCombobox"].get_active()
            player1 = playerItems[0].index(playerItems[variant_index][player1])
            diffi1 = int(cls.widgets["skillSlider2"].get_value())

            # Prepare players for ionest
            playertups = []
            for i, playerno, diffi, color in ((0, player0, diffi0, WHITE),
                                              (1, player1, diffi1, BLACK)):
                if playerno > 0:
                    engine = discoverer.getEngineN (playerno-1)
                    name = discoverer.getName(engine)
                    playertups.append((ARTIFICIAL, discoverer.initPlayerEngine,
                            (engine, color, diffi, variant, secs, incr), name))
                else:
                    playertups.append((LOCAL, Human, (color, ""), _("Human")))

            if secs > 0:
                timemodel = TimeModel (secs, incr)
            else: timemodel = None

            gamemodel = GameModel (timemodel, variant)

            callback(gamemodel, playertups[0], playertups[1])
Example #18
0
def loadFilesAndRun(uris):
    for uri in uris:
        loader = ionest.enddir[uri[uri.rfind(".") + 1:]]
        timemodel = TimeModel(0, 0)
        gamemodel = GameModel(timemodel)
        white_name = _("White")
        black_name = _("Black")
        p0 = (LOCAL, Human, (WHITE, white_name), white_name)
        p1 = (LOCAL, Human, (BLACK, black_name), black_name)
        ionest.generalStart(gamemodel, p0, p1, (uri, loader, 0, -1))
Example #19
0
def loadFileAndRun(uri):
    parts = splitUri(uri)
    uri = parts[1] if len(parts) == 2 else parts[0]
    loader = enddir[uri[uri.rfind(".") + 1:]]
    timemodel = TimeModel(0, 0)
    gamemodel = GameModel(timemodel)
    white_name = _("White")
    black_name = _("Black")
    p0 = (LOCAL, Human, (WHITE, white_name), white_name)
    p1 = (LOCAL, Human, (BLACK, black_name), black_name)
    game_handler.generalStart(gamemodel, p0, p1, (uri, loader, 0, -1))
Example #20
0
def start_lesson_from(filename, index=None):
    if filename.startswith("lichess_study"):
        chessfile = PGNFile(
            protoopen(addDataPrefix("learn/lessons/%s" % filename),
                      encoding="utf-8"))
    else:
        chessfile = PGNFile(
            protoopen(addDataPrefix("learn/lessons/%s" % filename)))
    chessfile.limit = 1000
    chessfile.init_tag_database()
    records, plys = chessfile.get_records()

    progress = lessons_solving_progress.get(filename, [0] * chessfile.count)

    if index is None:
        index = progress.index(0)

    rec = records[index]

    timemodel = TimeModel(0, 0)
    gamemodel = LearnModel(timemodel)
    gamemodel.set_learn_data(LESSON, filename, index, len(records))

    chessfile.loadToModel(rec, -1, gamemodel)

    color = gamemodel.boards[0].color
    player_name = conf.get("firstName", _("You"))

    w_name = player_name if color == WHITE else "PyChess"
    b_name = "PyChess" if color == WHITE else player_name

    p0 = (LOCAL, Human, (WHITE, w_name), w_name)
    p1 = (LOCAL, Human, (BLACK, b_name), b_name)

    def learn_success(gamemodel):
        chessfile.loadToModel(rec, -1, gamemodel)
        progress = lessons_solving_progress[gamemodel.source]
        progress[gamemodel.current_index] = 1
        lessons_solving_progress[gamemodel.source] = progress
        asyncio. async (gamemodel.restart_analyzer(HINT))

    gamemodel.connect("learn_success", learn_success)

    def on_game_started(gamemodel):
        perspective.activate_panel("annotationPanel")
        asyncio. async (gamemodel.start_analyzer(
            HINT, force_engine=discoverer.getEngineLearn()))

    gamemodel.connect("game_started", on_game_started)

    gamemodel.status = WAITING_TO_START
    perspective = perspective_manager.get_perspective("games")
    asyncio. async (perspective.generalStart(gamemodel, p0, p1))
Example #21
0
    def start_lecture_game(self):
        timemodel = TimeModel(0, 0)
        gamemodel = GameModel(timemodel)
        gamemodel.set_offline_lecture()
        white_name = black_name = "pychessbot"
        p0 = (LOCAL, Human, (WHITE, white_name), white_name)
        p1 = (LOCAL, Human, (BLACK, black_name), black_name)

        perspective = perspective_manager.get_perspective("games")
        asyncio.async(perspective.generalStart(gamemodel, p0, p1))

        return gamemodel
Example #22
0
    def onPlayGameCreated(self, bm, ficsgame):
        log.debug("FICS.onPlayGameCreated: %s" % ficsgame)

        for message in self.messages:
            message.dismiss()
        del self.messages[:]

        if self.connection.ICC:
            for button in self.quick_seek_buttons:
                button.set_active(False)

        timemodel = TimeModel(ficsgame.minutes * 60, ficsgame.inc)

        gamemodel = ICGameModel(self.connection, ficsgame, timemodel)
        gamemodel.connect("game_started", self.onGameModelStarted, ficsgame)

        wplayer, bplayer = ficsgame.wplayer, ficsgame.bplayer

        # We start
        if wplayer.name.lower() == self.connection.getUsername().lower():
            player0tup = (LOCAL, Human, (WHITE, wplayer.long_name(),
                                         wplayer.name,
                                         wplayer.getRatingForCurrentGame()),
                          wplayer.long_name())
            player1tup = (REMOTE, ICPlayer,
                          (gamemodel, bplayer.name, ficsgame.gameno, BLACK,
                           bplayer.long_name(),
                           bplayer.getRatingForCurrentGame()),
                          bplayer.long_name())

        # She starts
        else:
            player1tup = (LOCAL, Human, (BLACK, bplayer.long_name(),
                                         bplayer.name,
                                         bplayer.getRatingForCurrentGame()),
                          bplayer.long_name())
            player0tup = (REMOTE, ICPlayer,
                          (gamemodel, wplayer.name, ficsgame.gameno, WHITE,
                           wplayer.long_name(),
                           wplayer.getRatingForCurrentGame()),
                          wplayer.long_name())

        perspective = perspective_manager.get_perspective("games")
        if not ficsgame.board.fen:
            create_task(
                perspective.generalStart(gamemodel, player0tup, player1tup))
        else:
            create_task(
                perspective.generalStart(
                    gamemodel, player0tup, player1tup,
                    (StringIO(ficsgame.board.fen), fen, 0, -1)))
Example #23
0
def loadFileAndRun(uri):
    if uri in [None, '']:
        return False
    parts = splitUri(uri)
    uri = parts[1] if len(parts) == 2 else parts[0]
    loader = enddir[uri[uri.rfind(".") + 1:]]
    timemodel = TimeModel(0, 0)
    gamemodel = GameModel(timemodel)
    white_name = _("White")
    black_name = _("Black")
    p0 = (LOCAL, Human, (WHITE, white_name), white_name)
    p1 = (LOCAL, Human, (BLACK, black_name), black_name)
    perspective = perspective_manager.get_perspective("games")
    create_task(perspective.generalStart(gamemodel, p0, p1, (uri, loader, 0, -1)))
    return True
Example #24
0
def start_endgame_from(pieces):
        fen = create_fen(pieces)

        timemodel = TimeModel(0, 0)
        gamemodel = GameModel(timemodel)
        gamemodel.set_practice_game()
        gamemodel.practice = ("endgame", pieces)

        name = conf.get("firstName", _("You"))
        p0 = (LOCAL, Human, (WHITE, name), name)

        engine = discoverer.getEngineByName(stockfish_name)
        name = discoverer.getName(engine)
        p1 = (ARTIFICIAL, discoverer.initPlayerEngine,
              (engine, BLACK, 20, variants[NORMALCHESS], 60, 0, 0, True), name)

        perspective = perspective_manager.get_perspective("games")
        asyncio.async(perspective.generalStart(
            gamemodel, p0, p1, loaddata=(StringIO(fen), fen_loader, 0, -1)))
Example #25
0
def start_puzzle_from(filename):
    chessfile = PGNFile(protoopen(addDataPrefix("lectures/%s" % filename)))
    chessfile.limit = 1000
    importer = PgnImport(chessfile)
    chessfile.init_tag_database(importer)
    records, plys = chessfile.get_records()

    rec = records[random.randrange(0, len(records))]

    timemodel = TimeModel(0, 0)
    gamemodel = GameModel(timemodel)
    gamemodel.set_practice_game()
    gamemodel.practice = ("puzzle", filename)

    chessfile.loadToModel(rec, 0, gamemodel)

    # TODO: change colors according to FEN!
    name = rec["White"]
    p0 = (LOCAL, Human, (WHITE, name), name)

    engine = discoverer.getEngineByName(stockfish_name)
    name = rec["Black"]
    ponder_off = True
    p1 = (ARTIFICIAL, discoverer.initPlayerEngine,
          (engine, BLACK, 20, variants[NORMALCHESS], 60, 0, 0,
           ponder_off), name)

    def fix_name(gamemodel, name):
        asyncio. async (gamemodel.start_analyzer(HINT,
                                                 force_engine=stockfish_name))
        gamemodel.players[1].name = name
        gamemodel.emit("players_changed")

    gamemodel.connect("game_started", fix_name, name)

    gamemodel.variant.need_initial_board = True
    gamemodel.status = WAITING_TO_START

    perspective = perspective_manager.get_perspective("games")
    asyncio. async (perspective.generalStart(gamemodel, p0, p1))
Example #26
0
def createRematch (gamemodel):
    if gamemodel.timemodel:
        secs = gamemodel.timemodel.intervals[0][WHITE]
        gain = gamemodel.timemodel.gain
        newgamemodel = GameModel(TimeModel(secs, gain), gamemodel.variant)
    else:
        secs = 0
        gain = 0
        newgamemodel = GameModel(variant=gamemodel.variant)

    wp = gamemodel.players[WHITE]
    bp = gamemodel.players[BLACK]

    if wp.__type__ == LOCAL:
        player1tup = (wp.__type__, wp.__class__, (BLACK, ""), repr(wp))
        if bp.__type__ == LOCAL:
            player0tup = (bp.__type__, bp.__class__, (WHITE, ""), repr(bp))
        else:
            binname = bp.engine.path.split("/")[-1]
            if binname == "python":
                binname = bp.engine.args[1].split("/")[-1]
            xmlengine = discoverer.getEngines()[binname]
            player0tup = (ARTIFICIAL, discoverer.initPlayerEngine,
                          (xmlengine, WHITE, bp.strength, gamemodel.variant,
                           secs, gain), repr(bp))
    else:
        player0tup = (bp.__type__, bp.__class__, (WHITE, ""), repr(bp))
        binname = wp.engine.path.split("/")[-1]
        if binname == "python":
            binname = wp.engine.args[1].split("/")[-1]
        xmlengine = discoverer.getEngines()[binname]
        player1tup = (ARTIFICIAL, discoverer.initPlayerEngine,
                      (xmlengine, BLACK, wp.strength, gamemodel.variant,
                       secs, gain), repr(wp))

    ionest.generalStart(newgamemodel, player0tup, player1tup)
Example #27
0
class GameModel(GObject.GObject):
    """ GameModel contains all available data on a chessgame.
        It also has the task of controlling players actions and moves """

    __gsignals__ = {
        # game_started is emitted when control is given to the players for the
        # first time. Notice this is after players.start has been called.
        "game_started": (GObject.SignalFlags.RUN_FIRST, None, ()),
        # game_changed is emitted when a move has been made.
        "game_changed": (GObject.SignalFlags.RUN_FIRST, None, (int, )),
        # moves_undoig is emitted when a undoMoves call has been accepted, but
        # before any work has been done to execute it.
        "moves_undoing": (GObject.SignalFlags.RUN_FIRST, None, (int, )),
        # moves_undone is emitted after n moves have been undone in the
        # gamemodel and the players.
        "moves_undone": (GObject.SignalFlags.RUN_FIRST, None, (int, )),
        # variation_undoig is emitted when a undo_in_variation call has been started, but
        # before any work has been done to execute it.
        "variation_undoing": (GObject.SignalFlags.RUN_FIRST, None, ()),
        # variation_undone is emitted after 1 move have been undone in the
        # boardview shown variation
        "variation_undone": (GObject.SignalFlags.RUN_FIRST, None, ()),
        # game_unended is emitted if moves have been undone, such that the game
        # which had previously ended, is now again active.
        "game_unended": (GObject.SignalFlags.RUN_FIRST, None, ()),
        # game_loading is emitted if the GameModel is about to load in a chess
        # game from a file.
        "game_loading": (GObject.SignalFlags.RUN_FIRST, None, (object, )),
        # game_loaded is emitted after the chessformat handler has loaded in
        # all the moves from a file to the game model.
        "game_loaded": (GObject.SignalFlags.RUN_FIRST, None, (object, )),
        # game_saved is emitted in the end of model.save()
        "game_saved": (GObject.SignalFlags.RUN_FIRST, None, (str, )),
        # game_ended is emitted if the models state has been changed to an
        # "ended state"
        "game_ended": (GObject.SignalFlags.RUN_FIRST, None, (int, )),
        # game_terminated is emitted if the game was terminated. That is all
        # players and clocks were stopped, and it is no longer possible to
        # resume the game, even by undo.
        "game_terminated": (GObject.SignalFlags.RUN_FIRST, None, ()),
        # game_paused is emitted if the game was successfully paused.
        "game_paused": (GObject.SignalFlags.RUN_FIRST, None, ()),
        # game_paused is emitted if the game was successfully resumed from a
        # pause.
        "game_resumed": (GObject.SignalFlags.RUN_FIRST, None, ()),
        # action_error is currently only emitted by ICGameModel, in the case
        # the "web model" didn't accept the action you were trying to do.
        "action_error": (GObject.SignalFlags.RUN_FIRST, None, (object, int)),
        # players_changed is emitted if the players list was changed.
        "players_changed": (GObject.SignalFlags.RUN_FIRST, None, ()),
        "analyzer_added": (GObject.SignalFlags.RUN_FIRST, None, (object, str)),
        "analyzer_removed":
        (GObject.SignalFlags.RUN_FIRST, None, (object, str)),
        "analyzer_paused":
        (GObject.SignalFlags.RUN_FIRST, None, (object, str)),
        "analyzer_resumed":
        (GObject.SignalFlags.RUN_FIRST, None, (object, str)),
        # opening_changed is emitted if the move changed the opening.
        "opening_changed": (GObject.SignalFlags.RUN_FIRST, None, ()),
        # variation_added is emitted if a variation was added.
        "variation_added":
        (GObject.SignalFlags.RUN_FIRST, None, (object, object)),
        # variation_extended is emitted if a new move was added to a variation.
        "variation_extended": (GObject.SignalFlags.RUN_FIRST, None, (object,
                                                                     object)),
        # scores_changed is emitted if the analyzing scores was changed.
        "analysis_changed": (GObject.SignalFlags.RUN_FIRST, None, (int, )),
        # analysis_finished is emitted if the game analyzing finished stepping on all moves.
        "analysis_finished": (GObject.SignalFlags.RUN_FIRST, None, ()),
        # FICS games can get kibitz/whisper messages
        "message_received": (GObject.SignalFlags.RUN_FIRST, None, (str, str)),
        # FICS games can have observers
        "observers_received": (GObject.SignalFlags.RUN_FIRST, None, (str, )),
    }

    def __init__(self, timemodel=None, variant=NormalBoard):
        GObject.GObject.__init__(self)
        self.daemon = True
        self.variant = variant
        self.boards = [variant(setup=True)]

        self.moves = []
        self.scores = {}
        self.spy_scores = {}
        self.players = []

        self.gameno = None
        self.variations = [self.boards]

        self.terminated = False
        self.status = WAITING_TO_START
        self.reason = UNKNOWN_REASON
        self.curColor = WHITE

        # support algorithm for new players
        # type apparent : DecisionSupportAlgorithm
        self.support_algorithm = DecisionSupportAlgorithm()

        if timemodel is None:
            self.timemodel = TimeModel()
        else:
            self.timemodel = timemodel
        self.timemodel.gamemodel = self

        self.connections = collections.defaultdict(
            list)  # mainly for IC subclasses
        self.analyzer_cids = {}
        self.examined = False

        now = datetime.datetime.now()
        self.tags = collections.defaultdict(str)
        self.tags["Event"] = _("Local Event")
        self.tags["Site"] = _("Local Site")
        self.tags["Date"] = "%04d.%02d.%02d" % (now.year, now.month, now.day)
        self.tags["Round"] = "1"

        self.endstatus = None
        self.zero_reached_cid = None

        self.timed = self.timemodel.minutes != 0 or self.timemodel.gain != 0
        if self.timed:
            self.zero_reached_cid = self.timemodel.connect(
                'zero_reached', self.zero_reached)
            if self.timemodel.moves == 0:
                self.tags["TimeControl"] = "%d%s%d" % (
                    self.timemodel.minutes * 60,
                    "+" if self.timemodel.gain >= 0 else "-",
                    abs(self.timemodel.gain))
            else:
                self.tags["TimeControl"] = "%d/%d" % (
                    self.timemodel.moves, self.timemodel.minutes * 60)
            # Notice: tags["WhiteClock"] and tags["BlackClock"] are never set
            # on the gamemodel, but simply written or read during saving/
            # loading from pgn. If you want to know the time left for a player,
            # check the time model.

        # Keeps track of offers, so that accepts can be spotted
        self.offers = {}

        # True if the game has been changed since last save
        self.needsSave = False

        # The uri the current game was loaded from, or None if not a loaded game
        self.uri = None

        # Link to additiona info
        self.info = None

        self.spectators = {}

        self.undoQueue = Queue()

        # learn_type set by LearnModel.set_learn_data()
        self.offline_lecture = False
        self.puzzle_game = False
        self.lesson_game = False
        self.end_game = False
        self.solved = False

    @property
    def practice_game(self):
        return self.puzzle_game or self.end_game

    @property
    def starting_color(self):
        return BLACK if "FEN" in self.tags and self.tags["FEN"].split(
        )[1] == "b" else WHITE

    @property
    def orientation(self):
        if "Orientation" in self.tags:
            return BLACK if self.tags["Orintation"].lower(
            ) == "black" else WHITE
        else:
            return self.starting_color

    def zero_reached(self, timemodel, color):
        if conf.get('autoCallFlag'):
            if self.status == RUNNING and timemodel.getPlayerTime(color) <= 0:
                log.info(
                    'Automatically sending flag call on behalf of player %s.' %
                    self.players[1 - color].name)
                self.players[1 - color].emit("offer", Offer(FLAG_CALL))

    def __repr__(self):
        string = "<GameModel at %s" % id(self)
        string += " (ply=%s" % self.ply
        if len(self.moves) > 0:
            string += ", move=%s" % self.moves[-1]
        string += ", variant=%s" % self.variant.name.encode('utf-8')
        string += ", status=%s, reason=%s" % (str(self.status), str(
            self.reason))
        string += ", players=%s" % str(self.players)
        string += ", tags=%s" % str(self.tags)
        if len(self.boards) > 0:
            string += "\nboard=%s" % self.boards[-1]
        return string + ")>"

    @property
    def display_text(self):
        if self.variant == NormalBoard and not self.timed:
            return "[ " + _("Untimed") + " ]"
        else:
            text = "[ "
            if self.variant != NormalBoard:
                text += self.variant.name + " "
            if self.timed:
                text += self.timemodel.display_text + " "
            return text + "]"

    def setPlayers(self, players):
        log.debug("GameModel.setPlayers: starting")
        assert self.status == WAITING_TO_START
        self.players = players
        for player in self.players:
            self.connections[player].append(
                player.connect("offer", self.offerReceived))
            self.connections[player].append(
                player.connect("withdraw", self.withdrawReceived))
            self.connections[player].append(
                player.connect("decline", self.declineReceived))
            self.connections[player].append(
                player.connect("accept", self.acceptReceived))
        self.tags["White"] = str(self.players[WHITE])
        self.tags["Black"] = str(self.players[BLACK])
        log.debug("GameModel.setPlayers: -> emit players_changed")
        self.emit("players_changed")
        log.debug("GameModel.setPlayers: <- emit players_changed")
        log.debug("GameModel.setPlayers: returning")

        # when the players are set, it is known whether or not there is a bot
        # we activate the support algorithm if there is one
        # boolean to know if the game is against a bot
        # activate support algorithm if that is the case
        if self.isLocalGame():
            self.support_algorithm.set_foe_as_bot()

    def color(self, player):
        if player is self.players[0]:
            return WHITE
        else:
            return BLACK

    @asyncio.coroutine
    def start_analyzer(self, analyzer_type, force_engine=None):
        # Don't start regular analyzers
        if (self.practice_game or
                self.lesson_game) and force_engine is None and not self.solved:
            return

        # prevent starting new analyzers again and again
        # when fics lecture reuses the same gamemodel
        if analyzer_type in self.spectators:
            return

        from pychess.Players.engineNest import init_engine
        analyzer = yield from init_engine(analyzer_type,
                                          self,
                                          force_engine=force_engine)
        if analyzer is None:
            return

        analyzer.setOptionInitialBoard(self)
        # Enable to find alternate hint in learn perspective puzzles
        if force_engine is not None:
            analyzer.setOption("MultiPV", 3)
            analyzer.analysis_depth = 20

        self.spectators[analyzer_type] = analyzer
        self.emit("analyzer_added", analyzer, analyzer_type)
        self.analyzer_cids[analyzer_type] = analyzer.connect(
            "analyze", self.on_analyze)

    def remove_analyzer(self, analyzer_type):
        try:
            analyzer = self.spectators[analyzer_type]
        except KeyError:
            return

        analyzer.disconnect(self.analyzer_cids[analyzer_type])
        analyzer.end(KILLED, UNKNOWN_REASON)
        self.emit("analyzer_removed", analyzer, analyzer_type)
        del self.spectators[analyzer_type]

    def resume_analyzer(self, analyzer_type):
        try:
            analyzer = self.spectators[analyzer_type]
        except KeyError:
            return

        analyzer.resume()
        self.emit("analyzer_resumed", analyzer, analyzer_type)

    def pause_analyzer(self, analyzer_type):
        try:
            analyzer = self.spectators[analyzer_type]
        except KeyError:
            return

        analyzer.pause()
        self.emit("analyzer_paused", analyzer, analyzer_type)

    @asyncio.coroutine
    def restart_analyzer(self, analyzer_type):
        self.remove_analyzer(analyzer_type)
        yield from self.start_analyzer(analyzer_type)

    def on_analyze(self, analyzer, analysis):
        def safe_int(p):
            if p in [None, '']:
                return 0
            try:
                return int(p)
            except ValueError:
                return 0

        if analysis and (self.practice_game or self.lesson_game):
            for i, anal in enumerate(analysis):
                if anal is not None:
                    ply, pv, score, depth, nps = anal
                    if len(pv) > 0:
                        if ply not in self.hints:
                            self.hints[ply] = []

                        if len(self.hints[ply]) < i + 1:
                            self.hints[ply].append((pv[0], score))
                        else:
                            self.hints[ply][i] = (pv[0], score)
        if analysis and analysis[0] is not None:
            ply, pv, score, depth, nps = analysis[0]
            if score is not None and depth:
                if analyzer.mode == ANALYZING:
                    if (ply not in self.scores) or (safe_int(
                            self.scores[ply][2]) <= safe_int(depth)):
                        self.scores[ply] = (pv, score, depth)
                        self.emit("analysis_changed", ply)
                else:
                    if (ply not in self.spy_scores) or (safe_int(
                            self.spy_scores[ply][2]) <= safe_int(depth)):
                        self.spy_scores[ply] = (pv, score, depth)

    def setOpening(self, ply=None, redetermine=False):
        if ply is None:
            ply = self.ply

        opening = None
        while ply >= self.lowply:
            opening = get_eco(self.getBoardAtPly(ply).board.hash,
                              exactPosition=True)
            if opening is None and redetermine:
                ply = ply - 1
            else:
                break

        if opening is not None:
            self.tags["ECO"] = opening[0]
            self.tags["Opening"] = opening[1]
            self.tags["Variation"] = opening[2]
        else:
            if redetermine:
                if 'ECO' in self.tags:
                    del self.tags['ECO']
                if 'Opening' in self.tags:
                    del self.tags['Opening']
                if 'Variation' in self.tags:
                    del self.tags['Variation']
        self.emit("opening_changed")

    # Board stuff

    def _get_ply(self):
        return self.boards[-1].ply

    ply = property(_get_ply)

    def _get_lowest_ply(self):
        return self.boards[0].ply

    lowply = property(_get_lowest_ply)

    def _get_curplayer(self):
        try:
            return self.players[self.getBoardAtPly(self.ply).color]
        except IndexError:
            log.error("%s %s" %
                      (self.players, self.getBoardAtPly(self.ply).color))
            raise

    curplayer = property(_get_curplayer)

    def _get_waitingplayer(self):
        try:
            return self.players[1 - self.getBoardAtPly(self.ply).color]
        except IndexError:
            log.error("%s %s" %
                      (self.players, 1 - self.getBoardAtPly(self.ply).color))
            raise

    waitingplayer = property(_get_waitingplayer)

    def _plyToIndex(self, ply):
        index = ply - self.lowply
        if index < 0:
            raise IndexError("%s < %s\n" % (ply, self.lowply))
        return index

    def getBoardAtPly(self, ply, variation=0):
        try:
            return self.variations[variation][self._plyToIndex(ply)]
        except IndexError:
            log.error(
                "%d\t%d\t%d\t%d\t%d" %
                (self.lowply, ply, self.ply, variation, len(self.variations)))
            raise

    def getMoveAtPly(self, ply, variation=0):
        try:
            return Move(self.variations[variation][self._plyToIndex(ply) +
                                                   1].board.lastMove)
        except IndexError:
            log.error(
                "%d\t%d\t%d\t%d\t%d" %
                (self.lowply, ply, self.ply, variation, len(self.variations)))
            raise

    def hasLocalPlayer(self):
        if self.players[0].__type__ == LOCAL or self.players[
                1].__type__ == LOCAL:
            return True
        else:
            return False

    def hasEnginePlayer(self):
        if self.players[0].__type__ == ARTIFICIAL or self.players[
                1].__type__ == ARTIFICIAL:
            return True
        else:
            return False

    def isLocalGame(self):
        if self.players[0].__type__ != REMOTE and self.players[
                1].__type__ != REMOTE:
            return True
        else:
            return False

    def isObservationGame(self):
        return not self.hasLocalPlayer()

    def isEngine2EngineGame(self):
        if len(
                self.players
        ) == 2 and self.players[0].__type__ == ARTIFICIAL and self.players[
                1].__type__ == ARTIFICIAL:
            return True
        else:
            return False

    def isPlayingICSGame(self):
        if self.players and self.status in (WAITING_TO_START, PAUSED, RUNNING):
            if (self.players[0].__type__ == LOCAL and self.players[1].__type__ == REMOTE) or \
               (self.players[1].__type__ == LOCAL and self.players[0].__type__ == REMOTE) or \
               ((self.offline_lecture or self.practice_game or self.lesson_game) and not self.solved) or \
               (self.players[1].__type__ == REMOTE and self.players[0].__type__ == REMOTE and
                    self.examined and (
                    self.players[0].name == "puzzlebot" or self.players[1].name == "puzzlebot") or
                    self.players[0].name == "endgamebot" or self.players[1].name == "endgamebot"):
                return True
        return False

    def isLoadedGame(self):
        return self.gameno is not None

    # Offer management

    def offerReceived(self, player, offer):
        log.debug("GameModel.offerReceived: offerer=%s %s" %
                  (repr(player), offer))
        if player == self.players[WHITE]:
            opPlayer = self.players[BLACK]
        elif player == self.players[BLACK]:
            opPlayer = self.players[WHITE]
        else:
            # Player comments echoed to opponent if the player started a conversation
            # with you prior to observing a game the player is in #1113
            return

        if offer.type == HURRY_ACTION:
            opPlayer.hurry()

        elif offer.type == CHAT_ACTION:
            # print("GameModel.offerreceived(player, offer)", player.name, offer.param)
            opPlayer.putMessage(offer.param)

        elif offer.type == RESIGNATION:
            if player == self.players[WHITE]:
                self.end(BLACKWON, WON_RESIGN)
            else:
                self.end(WHITEWON, WON_RESIGN)

        elif offer.type == FLAG_CALL:
            assert self.timed
            if self.timemodel.getPlayerTime(1 - player.color) <= 0:
                if self.timemodel.getPlayerTime(player.color) <= 0:
                    self.end(DRAW, DRAW_CALLFLAG)
                elif not playerHasMatingMaterial(self.boards[-1],
                                                 player.color):
                    if player.color == WHITE:
                        self.end(DRAW, DRAW_WHITEINSUFFICIENTANDBLACKTIME)
                    else:
                        self.end(DRAW, DRAW_BLACKINSUFFICIENTANDWHITETIME)
                else:
                    if player == self.players[WHITE]:
                        self.end(WHITEWON, WON_CALLFLAG)
                    else:
                        self.end(BLACKWON, WON_CALLFLAG)
            else:
                player.offerError(offer, ACTION_ERROR_NOT_OUT_OF_TIME)

        elif offer.type == DRAW_OFFER and isClaimableDraw(self.boards[-1]):
            reason = getStatus(self.boards[-1])[1]
            self.end(DRAW, reason)

        elif offer.type == TAKEBACK_OFFER and offer.param < self.lowply:
            player.offerError(offer, ACTION_ERROR_TOO_LARGE_UNDO)

        elif offer.type in OFFERS:
            if offer not in self.offers:
                log.debug("GameModel.offerReceived: doing %s.offer(%s)" %
                          (repr(opPlayer), offer))
                self.offers[offer] = player
                opPlayer.offer(offer)
            # If we updated an older offer, we want to delete the old one
            keys = self.offers.keys()
            for offer_ in keys:
                if offer.type == offer_.type and offer != offer_:
                    del self.offers[offer_]

    def withdrawReceived(self, player, offer):
        log.debug("GameModel.withdrawReceived: withdrawer=%s %s" %
                  (repr(player), offer))
        if player == self.players[WHITE]:
            opPlayer = self.players[BLACK]
        else:
            opPlayer = self.players[WHITE]

        if offer in self.offers and self.offers[offer] == player:
            del self.offers[offer]
            opPlayer.offerWithdrawn(offer)
        else:
            player.offerError(offer, ACTION_ERROR_NONE_TO_WITHDRAW)

    def declineReceived(self, player, offer):
        log.debug("GameModel.declineReceived: decliner=%s %s" %
                  (repr(player), offer))
        if player == self.players[WHITE]:
            opPlayer = self.players[BLACK]
        else:
            opPlayer = self.players[WHITE]

        if offer in self.offers and self.offers[offer] == opPlayer:
            del self.offers[offer]
            log.debug("GameModel.declineReceived: declining %s" % offer)
            opPlayer.offerDeclined(offer)
        else:
            player.offerError(offer, ACTION_ERROR_NONE_TO_DECLINE)

    def acceptReceived(self, player, offer):
        log.debug("GameModel.acceptReceived: accepter=%s %s" %
                  (repr(player), offer))
        if player == self.players[WHITE]:
            opPlayer = self.players[BLACK]
        else:
            opPlayer = self.players[WHITE]

        if offer in self.offers and self.offers[offer] == opPlayer:
            if offer.type == DRAW_OFFER:
                self.end(DRAW, DRAW_AGREE)
            elif offer.type == TAKEBACK_OFFER:
                log.debug("GameModel.acceptReceived: undoMoves(%s)" %
                          offer.param)
                self.undoMoves(offer.param)
            elif offer.type == ADJOURN_OFFER:
                self.end(ADJOURNED, ADJOURNED_AGREEMENT)
            elif offer.type == ABORT_OFFER:
                self.end(ABORTED, ABORTED_AGREEMENT)
            elif offer.type == PAUSE_OFFER:
                self.pause()
            elif offer.type == RESUME_OFFER:
                self.resume()
            del self.offers[offer]
        else:
            player.offerError(offer, ACTION_ERROR_NONE_TO_ACCEPT)

    # Data stuff

    def loadAndStart(self, uri, loader, gameno, position, first_time=True):
        if first_time:
            assert self.status == WAITING_TO_START

        uriIsFile = not isinstance(uri, str)
        if not uriIsFile:
            chessfile = loader.load(protoopen(uri))
        else:
            chessfile = loader.load(uri)

        self.gameno = gameno
        self.emit("game_loading", uri)
        try:
            chessfile.loadToModel(gameno, -1, self)
        # Postpone error raising to make games loadable to the point of the
        # error
        except LoadingError as e:
            error = e
        else:
            error = None
        if self.players:
            self.players[WHITE].setName(self.tags["White"])
            self.players[BLACK].setName(self.tags["Black"])
        self.emit("game_loaded", uri)

        self.needsSave = False
        if not uriIsFile:
            self.uri = uri
        else:
            self.uri = None

        # Even if the game "starts ended", the players should still be moved
        # to the last position, so analysis is correct, and a possible "undo"
        # will work as expected.
        for spectator in self.spectators.values():
            spectator.setOptionInitialBoard(self)
        for player in self.players:
            player.setOptionInitialBoard(self)
        if self.timed:
            self.timemodel.setMovingColor(self.boards[-1].color)

        if first_time:
            if self.status == RUNNING:
                if self.timed:
                    self.timemodel.start()

            # Store end status from Result tag
            if self.status in (DRAW, WHITEWON, BLACKWON):
                self.endstatus = self.status
            self.status = WAITING_TO_START
            self.start()

        if error:
            raise error

    def save(self, uri, saver, append, position=None, flip=False):
        if saver in (html, txt):
            fileobj = open(uri,
                           "a" if append else "w",
                           encoding="utf-8",
                           newline="")
            self.uri = uri
        elif isinstance(uri, str):
            fileobj = protosave(uri, append)
            self.uri = uri
        else:
            fileobj = uri
            self.uri = None
        saver.save(fileobj, self, position, flip)
        self.needsSave = False
        self.emit("game_saved", uri)

    def get_book_move(self):
        openings = getOpenings(self.boards[-1].board)
        openings.sort(key=lambda t: t[1], reverse=True)
        if not openings:
            return None

        total_weights = 0
        for move, weight, learn in openings:
            total_weights += weight

        if total_weights < 1:
            return None

        choice = random.randint(0, total_weights - 1)

        current_sum = 0
        for move, weight, learn in openings:
            current_sum += weight
            if current_sum > choice:
                return Move(move)

    # Run stuff

    def start(self):
        @asyncio.coroutine
        def coro():
            log.debug("GameModel.run: Starting. self=%s" % self)
            # Avoid racecondition when self.start is called while we are in
            # self.end
            if self.status != WAITING_TO_START:
                return

            if not self.isLocalGame():
                self.timemodel.handle_gain = False

            self.status = RUNNING

            for player in self.players + list(self.spectators.values()):
                event = asyncio.Event()
                is_dead = set()
                player.start(event, is_dead)

                yield from event.wait()

                if is_dead:
                    if player in self.players[WHITE]:
                        self.kill(WHITE_ENGINE_DIED)
                        break
                    elif player in self.players[BLACK]:
                        self.kill(BLACK_ENGINE_DIED)
                        break

            log.debug("GameModel.run: emitting 'game_started' self=%s" % self)
            self.emit("game_started")

            # Let GameModel end() itself on games started with loadAndStart()
            if not self.lesson_game:
                self.checkStatus()

            if self.isEngine2EngineGame() and self.timed:
                self.timemodel.start()
                self.timemodel.started = True

            self.curColor = self.boards[-1].color

            book_depth_max = conf.get("book_depth_max")

            while self.status in (PAUSED, RUNNING, DRAW, WHITEWON, BLACKWON):
                curPlayer = self.players[self.curColor]

                if self.timed:
                    log.debug(
                        "GameModel.run: id=%s, players=%s, self.ply=%s: updating %s's time"
                        % (id(self), str(self.players), str(
                            self.ply), str(curPlayer)))
                    curPlayer.updateTime(
                        self.timemodel.getPlayerTime(self.curColor),
                        self.timemodel.getPlayerTime(1 - self.curColor))
                try:
                    log.debug(
                        "GameModel.run: id=%s, players=%s, self.ply=%s: calling %s.makeMove()"
                        % (id(self), str(
                            self.players), self.ply, str(curPlayer)))

                    move = None
                    # if the current player is a bot
                    if curPlayer.__type__ == ARTIFICIAL and book_depth_max > 0 and self.ply <= book_depth_max:
                        move = self.get_book_move()
                        log.debug(
                            "GameModel.run: id=%s, players=%s, self.ply=%s: got move=%s from book"
                            % (id(self), str(self.players), self.ply, move))
                        if move is not None:
                            curPlayer.set_board(self.boards[-1].move(move))
                    # if the current player is not a bot
                    if move is None:

                        if self.ply > self.lowply:
                            move = yield from curPlayer.makeMove(
                                self.boards[-1], self.moves[-1],
                                self.boards[-2])
                        else:
                            move = yield from curPlayer.makeMove(
                                self.boards[-1], None, None)
                        log.debug(
                            "GameModel.run: id=%s, players=%s, self.ply=%s: got move=%s from %s"
                            % (id(self), str(
                                self.players), self.ply, move, str(curPlayer)))
                except PlayerIsDead as e:
                    if self.status in (WAITING_TO_START, PAUSED, RUNNING):
                        stringio = StringIO()
                        traceback.print_exc(file=stringio)
                        error = stringio.getvalue()
                        log.error(
                            "GameModel.run: A Player died: player=%s error=%s\n%s"
                            % (curPlayer, error, e))
                        if self.curColor == WHITE:
                            self.kill(WHITE_ENGINE_DIED)
                        else:
                            self.kill(BLACK_ENGINE_DIED)
                    break
                except InvalidMove as e:
                    stringio = StringIO()
                    traceback.print_exc(file=stringio)
                    error = stringio.getvalue()
                    log.error(
                        "GameModel.run: InvalidMove by player=%s error=%s\n%s"
                        % (curPlayer, error, e))
                    if self.curColor == WHITE:
                        self.end(BLACKWON, WON_ADJUDICATION)
                    else:
                        self.end(WHITEWON, WON_ADJUDICATION)
                    break
                except PassInterrupt:
                    log.debug(
                        "GameModel.run: id=%s, players=%s, self.ply=%s: PassInterrupt"
                        % (id(self), str(self.players), self.ply))
                    continue
                except TurnInterrupt:
                    log.debug(
                        "GameModel.run: id=%s, players=%s, self.ply=%s: TurnInterrupt"
                        % (id(self), str(self.players), self.ply))
                    self.curColor = self.boards[-1].color
                    continue
                except GameEnded:
                    log.debug("GameModel.run: got GameEnded exception")
                    break

                assert isinstance(move, Move), "%s" % repr(move)
                log.debug(
                    "GameModel.run: id=%s, players=%s, self.ply=%s: applying move=%s"
                    % (id(self), str(self.players), self.ply, str(move)))
                self.needsSave = True
                newBoard = self.boards[-1].move(move)
                newBoard.board.prev = self.boards[-1].board

                # newBoard.printPieces()
                # Variation on next move can exist from the hint panel...
                if self.boards[-1].board.next is not None:
                    newBoard.board.children = self.boards[
                        -1].board.next.children

                self.boards = self.variations[0]
                self.boards[-1].board.next = newBoard.board
                self.boards.append(newBoard)
                self.moves.append(move)

                if self.timed:
                    self.timemodel.tap()

                if not self.terminated:
                    self.emit("game_changed", self.ply)

                for spectator in self.spectators.values():
                    if spectator.board == self.boards[-2]:
                        spectator.putMove(self.boards[-1], self.moves[-1],
                                          self.boards[-2])

                if self.puzzle_game and len(self.moves) % 2 == 1:
                    status, reason = getStatus(self.boards[-1])
                    self.failed_playing_best = self.check_failed_playing_best(
                        status)
                    if self.failed_playing_best:
                        # print("failed_playing_best() == True -> yield from asyncio.sleep(1.5) ")
                        # It may happen that analysis had no time to fill hints with best moves
                        # so we give him another chance with some additional time to think on it
                        self.spectators[HINT].setBoard(self.boards[-2])
                        # TODO: wait for an event (analyzer PV reaching 18 ply)
                        # instead of hard coded sleep time
                        yield from asyncio.sleep(1.5)
                        self.failed_playing_best = self.check_failed_playing_best(
                            status)

                self.checkStatus()

                self.setOpening()

                self.curColor = 1 - self.curColor

            self.checkStatus()

        create_task(coro())

    def checkStatus(self):
        """ Updates self.status so it fits with what getStatus(boards[-1])
            would return. That is, if the game is e.g. check mated this will
            call mode.end(), or if moves have been undone from an otherwise
            ended position, this will call __resume and emit game_unended. """
        log.debug("GameModel.checkStatus:")

        # call flag by engine
        if self.isEngine2EngineGame() and self.status in UNDOABLE_STATES:
            return

        status, reason = getStatus(self.boards[-1])

        if self.practice_game and (len(self.moves) % 2 == 1
                                   or status in UNDOABLE_STATES):
            self.check_goal(status, reason)

        if self.endstatus is not None:
            self.end(self.endstatus, reason)
            return

        if status != RUNNING and self.status in (WAITING_TO_START, PAUSED,
                                                 RUNNING):
            if status == DRAW and reason in (DRAW_REPETITION, DRAW_50MOVES):
                if self.isEngine2EngineGame():
                    self.end(status, reason)
                    return
            else:
                self.end(status, reason)
                return

        if status != self.status and self.status in UNDOABLE_STATES \
                and self.reason in UNDOABLE_REASONS:
            self.__resume()
            self.status = status
            self.reason = UNKNOWN_REASON
            self.emit("game_unended")

    def __pause(self):
        log.debug("GameModel.__pause: %s" % self)
        if self.isEngine2EngineGame():
            for player in self.players:
                player.end(self.status, self.reason)
            if self.timed:
                self.timemodel.end()
        else:
            for player in self.players:
                player.pause()
            if self.timed:
                self.timemodel.pause()

    def pause(self):
        """ Players will raise NotImplementedError if they doesn't support
            pause. Spectators will be ignored. """

        self.__pause()
        self.status = PAUSED
        self.emit("game_paused")

    def __resume(self):
        for player in self.players:
            player.resume()
        if self.timed:
            self.timemodel.resume()
        self.emit("game_resumed")

    def resume(self):
        self.status = RUNNING
        self.__resume()

    def end(self, status, reason):
        if self.status not in UNFINISHED_STATES:
            log.info(
                "GameModel.end: Can't end a game that's already ended: %s %s" %
                (status, reason))
            return
        if self.status not in (WAITING_TO_START, PAUSED, RUNNING):
            self.needsSave = True

        log.debug(
            "GameModel.end: players=%s, self.ply=%s: Ending a game with status %d for reason %d"
            % (repr(self.players), str(self.ply), status, reason))
        self.status = status
        self.reason = reason

        self.emit("game_ended", reason)

        self.__pause()

    def kill(self, reason):
        log.debug(
            "GameModel.kill: players=%s, self.ply=%s: Killing a game for reason %d\n%s"
            % (repr(self.players), str(self.ply), reason, "".join(
                traceback.format_list(traceback.extract_stack())).strip()))

        self.status = KILLED
        self.reason = reason

        for player in self.players:
            player.end(self.status, reason)

        for spectator in self.spectators.values():
            spectator.end(self.status, reason)

        if self.timed:
            self.timemodel.end()

        self.emit("game_ended", reason)

    def terminate(self):
        log.debug("GameModel.terminate: %s" % self)
        self.terminated = True

        if self.status != KILLED:
            for player in self.players:
                player.end(self.status, self.reason)

            analyzer_types = list(self.spectators.keys())
            for analyzer_type in analyzer_types:
                self.remove_analyzer(analyzer_type)

            if self.timed:
                log.debug("GameModel.terminate: -> timemodel.end()")
                self.timemodel.end()
                log.debug("GameModel.terminate: <- timemodel.end() %s" %
                          repr(self.timemodel))
                if self.zero_reached_cid is not None:
                    self.timemodel.disconnect(self.zero_reached_cid)

        # ICGameModel may did this if game was a FICS game
        if self.connections is not None:
            for player in self.players:
                for cid in self.connections[player]:
                    player.disconnect(cid)
        self.connections = {}

        self.timemodel.gamemodel = None
        self.players = []
        self.emit("game_terminated")

    # Other stuff

    def undoMoves(self, moves):
        """ Undo and remove moves number of moves from the game history from
            the GameModel, players, and any spectators """
        if self.ply < 1 or moves < 1:
            return
        if self.ply - moves < 0:
            # There is no way in the current threaded/asynchronous design
            # for the GUI to know that the number of moves it requests to takeback
            # will still be valid once the undo is actually processed. So, until
            # we either add some locking or get a synchronous design, we quietly
            # "fix" the takeback request rather than cause AssertionError or IndexError
            moves = 1

        log.debug(
            "GameModel.undoMoves: players=%s, self.ply=%s, moves=%s, board=%s"
            % (repr(self.players), self.ply, moves, self.boards[-1]))
        self.emit("moves_undoing", moves)
        self.needsSave = True

        self.boards = self.variations[0]
        del self.boards[-moves:]
        del self.moves[-moves:]
        self.boards[-1].board.next = None

        for player in self.players:
            player.playerUndoMoves(moves, self)
        for spectator in self.spectators.values():
            spectator.spectatorUndoMoves(moves, self)

        log.debug("GameModel.undoMoves: undoing timemodel")
        if self.timed:
            self.timemodel.undoMoves(moves)

        self.checkStatus()
        self.setOpening(redetermine=True)

        self.emit("moves_undone", moves)

    def isChanged(self):
        if self.ply == 0:
            return False
        if self.needsSave:
            return True
        # what was this for?
        # if not self.uri or not isWriteable(self.uri):
        # return True
        return False

    def add_variation(self, board, moves, comment="", score="", emit=True):
        if board.board.next is None:
            # If we are in the latest played board, and want to add a variation
            # we have to add the latest move first
            if board.board.lastMove is None or board.board.prev is None:
                return
            moves = [Move(board.board.lastMove)] + moves
            board = board.board.prev.pieceBoard

        board0 = board
        board = board0.clone()
        board.board.prev = None

        # this prevents annotation panel node searches to find this instead of board0
        board.board.hash = -1

        if comment:
            board.board.children.append(comment)

        variation = [board]

        for move in moves:
            new = board.move(move)
            if len(variation) == 1:
                new.board.prev = board0.board
                variation[0].board.next = new.board
            else:
                new.board.prev = board.board
                board.board.next = new.board
            variation.append(new)
            board = new

        board0.board.next.children.append(
            [vboard.board for vboard in variation])
        if score:
            variation[-1].board.children.append(score)

        head = None
        for vari in self.variations:
            if board0 in vari:
                head = vari
                break

        variation[0] = board0
        self.variations.append(head[:board0.ply - self.lowply] + variation)
        self.needsSave = True
        if emit:
            self.emit("variation_added", board0.board.next.children[-1],
                      board0.board.next)
        return self.variations[-1]

    def add_move2variation(self, board, move, variationIdx):
        new = board.move(move)
        new.board.prev = board.board
        board.board.next = new.board

        # Find the variation (low level lboard list) to append
        cur_board = board.board
        vari = None
        while cur_board.prev is not None:
            for child in cur_board.prev.next.children:
                if isinstance(child, list) and cur_board in child:
                    vari = child
                    break
            if vari is None:
                cur_board = cur_board.prev
            else:
                break
        vari.append(new.board)

        self.variations[variationIdx].append(new)
        self.needsSave = True
        self.emit("variation_extended", board.board, new.board)

    def remove_variation(self, board, parent):
        """ board must be an lboard object of the first Board object of a variation Board(!) list """
        # Remove the variation (list of lboards) containing board from parent's children list
        for child in parent.children:
            if isinstance(child, list) and board in child:
                parent.children.remove(child)
                break

        # Remove all variations from gamemodel's variations list which contains this board
        for vari in self.variations[1:]:
            if board.pieceBoard in vari:
                self.variations.remove(vari)

        # remove null_board if variation was added on last played move
        if not parent.fen_was_applied:
            parent.prev.next = None

        self.needsSave = True

    def undo_in_variation(self, board):
        """ board must be the latest Board object of a variation board list """
        assert board.board.next is None and len(board.board.children) == 0
        self.emit("variation_undoing")

        for vari in self.variations[1:]:
            if board in vari:
                break

        board = board.board
        parent = board.prev.next

        # If this is a one move only variation we have to remove the whole variation
        # if it's a longer one, just remove the latest move from it
        first_vari_moves = [
            child[1] for child in parent.children
            if not isinstance(child, str)
        ]
        if board in first_vari_moves:
            self.remove_variation(board, parent)
        else:
            board.prev.next = None
            del vari[-1]

        self.needsSave = True
        self.emit("variation_undone")
Example #28
0
    def __init__(self, timemodel=None, variant=NormalBoard):
        GObject.GObject.__init__(self)
        self.daemon = True
        self.variant = variant
        self.boards = [variant(setup=True)]

        self.moves = []
        self.scores = {}
        self.spy_scores = {}
        self.players = []

        self.gameno = None
        self.variations = [self.boards]

        self.terminated = False
        self.status = WAITING_TO_START
        self.reason = UNKNOWN_REASON
        self.curColor = WHITE

        # support algorithm for new players
        # type apparent : DecisionSupportAlgorithm
        self.support_algorithm = DecisionSupportAlgorithm()

        if timemodel is None:
            self.timemodel = TimeModel()
        else:
            self.timemodel = timemodel
        self.timemodel.gamemodel = self

        self.connections = collections.defaultdict(
            list)  # mainly for IC subclasses
        self.analyzer_cids = {}
        self.examined = False

        now = datetime.datetime.now()
        self.tags = collections.defaultdict(str)
        self.tags["Event"] = _("Local Event")
        self.tags["Site"] = _("Local Site")
        self.tags["Date"] = "%04d.%02d.%02d" % (now.year, now.month, now.day)
        self.tags["Round"] = "1"

        self.endstatus = None
        self.zero_reached_cid = None

        self.timed = self.timemodel.minutes != 0 or self.timemodel.gain != 0
        if self.timed:
            self.zero_reached_cid = self.timemodel.connect(
                'zero_reached', self.zero_reached)
            if self.timemodel.moves == 0:
                self.tags["TimeControl"] = "%d%s%d" % (
                    self.timemodel.minutes * 60,
                    "+" if self.timemodel.gain >= 0 else "-",
                    abs(self.timemodel.gain))
            else:
                self.tags["TimeControl"] = "%d/%d" % (
                    self.timemodel.moves, self.timemodel.minutes * 60)
            # Notice: tags["WhiteClock"] and tags["BlackClock"] are never set
            # on the gamemodel, but simply written or read during saving/
            # loading from pgn. If you want to know the time left for a player,
            # check the time model.

        # Keeps track of offers, so that accepts can be spotted
        self.offers = {}

        # True if the game has been changed since last save
        self.needsSave = False

        # The uri the current game was loaded from, or None if not a loaded game
        self.uri = None

        # Link to additiona info
        self.info = None

        self.spectators = {}

        self.undoQueue = Queue()

        # learn_type set by LearnModel.set_learn_data()
        self.offline_lecture = False
        self.puzzle_game = False
        self.lesson_game = False
        self.end_game = False
        self.solved = False
Example #29
0
class GameModel(GObject.GObject, Thread):
    """ GameModel contains all available data on a chessgame.
        It also has the task of controlling players actions and moves """

    __gsignals__ = {
        # game_started is emitted when control is given to the players for the
        # first time. Notice this is after players.start has been called.
        "game_started": (GObject.SignalFlags.RUN_FIRST, None, ()),
        # game_changed is emitted when a move has been made.
        "game_changed": (GObject.SignalFlags.RUN_FIRST, None, (int, )),
        # moves_undoig is emitted when a undoMoves call has been accepted, but
        # before anywork has been done to execute it.
        "moves_undoing": (GObject.SignalFlags.RUN_FIRST, None, (int, )),
        # moves_undone is emitted after n moves have been undone in the
        # gamemodel and the players.
        "moves_undone": (GObject.SignalFlags.RUN_FIRST, None, (int, )),
        # game_unended is emitted if moves have been undone, such that the game
        # which had previously ended, is now again active.
        "game_unended": (GObject.SignalFlags.RUN_FIRST, None, ()),
        # game_loading is emitted if the GameModel is about to load in a chess
        # game from a file.
        "game_loading": (GObject.SignalFlags.RUN_FIRST, None, (object, )),
        # game_loaded is emitted after the chessformat handler has loaded in
        # all the moves from a file to the game model.
        "game_loaded": (GObject.SignalFlags.RUN_FIRST, None, (object, )),
        # game_saved is emitted in the end of model.save()
        "game_saved": (GObject.SignalFlags.RUN_FIRST, None, (str, )),
        # game_ended is emitted if the models state has been changed to an
        # "ended state"
        "game_ended": (GObject.SignalFlags.RUN_FIRST, None, (int, )),
        # game_terminated is emitted if the game was terminated. That is all
        # players and clocks were stopped, and it is no longer possible to
        # resume the game, even by undo.
        "game_terminated": (GObject.SignalFlags.RUN_FIRST, None, ()),
        # game_paused is emitted if the game was successfully paused.
        "game_paused": (GObject.SignalFlags.RUN_FIRST, None, ()),
        # game_paused is emitted if the game was successfully resumed from a
        # pause.
        "game_resumed": (GObject.SignalFlags.RUN_FIRST, None, ()),
        # action_error is currently only emitted by ICGameModel, in the case
        # the "web model" didn't accept the action you were trying to do.
        "action_error": (GObject.SignalFlags.RUN_FIRST, None, (object, int)),
        # players_changed is emitted if the players list was changed.
        "players_changed": (GObject.SignalFlags.RUN_FIRST, None, ()),
        "analyzer_added": (GObject.SignalFlags.RUN_FIRST, None, (object, str)),
        "analyzer_removed": (GObject.SignalFlags.RUN_FIRST, None,
                             (object, str)),
        "analyzer_paused": (GObject.SignalFlags.RUN_FIRST, None,
                            (object, str)),
        "analyzer_resumed": (GObject.SignalFlags.RUN_FIRST, None,
                             (object, str)),
        # opening_changed is emitted if the move changed the opening.
        "opening_changed": (GObject.SignalFlags.RUN_FIRST, None, ()),
        # variation_added is emitted if a variation was added.
        "variation_added": (GObject.SignalFlags.RUN_FIRST, None,
                            (object, object, str, str)),
        # variation_extended is emitted if a new move was added to a variation.
        "variation_extended": (GObject.SignalFlags.RUN_FIRST, None,
                               (object, object)),
        # scores_changed is emitted if the analyzing scores was changed.
        "analysis_changed": (GObject.SignalFlags.RUN_FIRST, None, (int, )),
        # FICS games can get kibitz/whisper messages
        "message_received": (GObject.SignalFlags.RUN_FIRST, None, (str, str)),
        # FICS games can have observers
        "observers_received": (GObject.SignalFlags.RUN_FIRST, None, (str, )),
    }

    def __init__(self, timemodel=None, variant=NormalBoard):
        GObject.GObject.__init__(self)
        Thread.__init__(self, name=fident(self.run))
        self.daemon = True
        self.variant = variant
        self.boards = [variant(setup=True)]

        self.moves = []
        self.scores = {}
        self.spy_scores = {}
        self.players = []

        self.gameno = None
        self.variations = [self.boards]

        self.terminated = False
        self.status = WAITING_TO_START
        self.reason = UNKNOWN_REASON
        self.curColor = WHITE

        if timemodel is None:
            self.timemodel = TimeModel()
        else:
            self.timemodel = timemodel
        self.timemodel.gamemodel = self

        self.connections = defaultdict(list)  # mainly for IC subclasses
        self.analyzer_cids = {}
        self.examined = False

        now = datetime.datetime.now()
        self.tags = {
            "Event": _("Local Event"),
            "Site": _("Local Site"),
            "Round": 1,
            "Year": now.year,
            "Month": now.month,
            "Day": now.day,
            "Time": "%02d:%02d:00" % (now.hour, now.minute),
            "Result": "*",
        }

        self.endstatus = None
        self.timed = self.timemodel.minutes != 0 or self.timemodel.gain != 0
        if self.timed:
            self.zero_reached_cid = self.timemodel.connect('zero_reached', self.zero_reached)

            self.tags["TimeControl"] = \
                "%d+%d" % (self.timemodel.minutes * 60, self.timemodel.gain)
            # Notice: tags["WhiteClock"] and tags["BlackClock"] are never set
            # on the gamemodel, but simply written or read during saving/
            # loading from pgn. If you want to know the time left for a player,
            # check the time model.

            # Keeps track of offers, so that accepts can be spotted
        self.offers = {}
        # True if the game has been changed since last save
        self.needsSave = False
        # The uri the current game was loaded from, or None if not a loaded
        # game
        self.uri = None

        self.spectators = {}

        self.applyingMoveLock = RLock()
        self.undoLock = RLock()
        self.undoQueue = Queue()

    def zero_reached(self, timemodel, color):
        if conf.get('autoCallFlag', False) and self.players[1 - color].__type__ == ARTIFICIAL:
            if self.status == RUNNING and timemodel.getPlayerTime(color) <= 0:
                log.info(
                    'Automatically sending flag call on behalf of player %s.' %
                    self.players[1 - color].name)
                self.players[1 - color].emit("offer", Offer(FLAG_CALL))

    def __repr__(self):
        string = "<GameModel at %s" % id(self)
        string += " (ply=%s" % self.ply
        if len(self.moves) > 0:
            string += ", move=%s" % self.moves[-1]
        string += ", variant=%s" % self.variant.name.encode('utf-8')
        string += ", status=%s, reason=%s" % (str(self.status), str(self.reason))
        string += ", players=%s" % str(self.players)
        string += ", tags=%s" % str(self.tags)
        if len(self.boards) > 0:
            string += "\nboard=%s" % self.boards[-1]
        return string + ")>"

    @property
    def display_text(self):
        if self.variant == NormalBoard and not self.timed:
            return "[ " + _("Untimed") + " ]"
        else:
            text = "[ "
            if self.variant != NormalBoard:
                text += self.variant.name + " "
            if self.timed:
                text += self.timemodel.display_text + " "
            return text + "]"

    def setPlayers(self, players):
        log.debug("GameModel.setPlayers: starting")
        assert self.status == WAITING_TO_START
        self.players = players
        for player in self.players:
            self.connections[player].append(player.connect("offer",
                                                           self.offerReceived))
            self.connections[player].append(player.connect(
                "withdraw", self.withdrawReceived))
            self.connections[player].append(player.connect(
                "decline", self.declineReceived))
            self.connections[player].append(player.connect(
                "accept", self.acceptReceived))
        self.tags["White"] = str(self.players[WHITE])
        self.tags["Black"] = str(self.players[BLACK])
        log.debug("GameModel.setPlayers: -> emit players_changed")
        self.emit("players_changed")
        log.debug("GameModel.setPlayers: <- emit players_changed")
        log.debug("GameModel.setPlayers: returning")

    def color(self, player):
        if player is self.players[0]:
            return WHITE
        else:
            return BLACK

    def start_analyzer(self, analyzer_type):
        from pychess.Players.engineNest import init_engine
        analyzer = init_engine(analyzer_type, self)
        if analyzer is None:
            return

        analyzer.setOptionInitialBoard(self)
        self.spectators[analyzer_type] = analyzer
        self.emit("analyzer_added", analyzer, analyzer_type)
        self.analyzer_cids[analyzer_type] = analyzer.connect("analyze", self.on_analyze)
        return analyzer

    def remove_analyzer(self, analyzer_type):
        try:
            analyzer = self.spectators[analyzer_type]
        except KeyError:
            return

        analyzer.disconnect(self.analyzer_cids[analyzer_type])
        analyzer.end(KILLED, UNKNOWN_REASON)
        self.emit("analyzer_removed", analyzer, analyzer_type)
        del self.spectators[analyzer_type]

    def resume_analyzer(self, analyzer_type):
        try:
            analyzer = self.spectators[analyzer_type]
        except KeyError:
            analyzer = self.start_analyzer(analyzer_type)
            if analyzer is None:
                return

        analyzer.resume()
        analyzer.setOptionInitialBoard(self)
        self.emit("analyzer_resumed", analyzer, analyzer_type)

    def pause_analyzer(self, analyzer_type):
        try:
            analyzer = self.spectators[analyzer_type]
        except KeyError:
            return

        analyzer.pause()
        self.emit("analyzer_paused", analyzer, analyzer_type)

    def restart_analyzer(self, analyzer_type):
        self.remove_analyzer(analyzer_type)
        self.start_analyzer(analyzer_type)
        if self.isPlayingICSGame():
            self.pause_analyzer(analyzer_type)

    def on_analyze(self, analyzer, analysis):
        if analysis and analysis[0] is not None:
            pv, score, depth = analysis[0]
            ply = analyzer.board.ply
            if score is not None:
                if analyzer.mode == ANALYZING:
                    self.scores[ply] = (pv, score, depth)
                    self.emit("analysis_changed", ply)
                else:
                    self.spy_scores[ply] = (pv, score, depth)

    def setOpening(self, ply=None):
        if ply is None:
            ply = self.ply
        if ply > 40:
            return

        if ply > 0:
            opening = get_eco(self.getBoardAtPly(ply).board.hash)
        else:
            opening = ("", "", "")
        if opening is not None:
            self.tags["ECO"] = opening[0]
            self.tags["Opening"] = opening[1]
            self.tags["Variation"] = opening[2]
            self.emit("opening_changed")

    # Board stuff

    def _get_ply(self):
        return self.boards[-1].ply

    ply = property(_get_ply)

    def _get_lowest_ply(self):
        return self.boards[0].ply

    lowply = property(_get_lowest_ply)

    def _get_curplayer(self):
        try:
            return self.players[self.getBoardAtPly(self.ply).color]
        except IndexError:
            log.error("%s %s" %
                      (self.players, self.getBoardAtPly(self.ply).color))
            raise

    curplayer = property(_get_curplayer)

    def _get_waitingplayer(self):
        try:
            return self.players[1 - self.getBoardAtPly(self.ply).color]
        except IndexError:
            log.error("%s %s" %
                      (self.players, 1 - self.getBoardAtPly(self.ply).color))
            raise

    waitingplayer = property(_get_waitingplayer)

    def _plyToIndex(self, ply):
        index = ply - self.lowply
        if index < 0:
            raise IndexError("%s < %s\n" % (ply, self.lowply))
        return index

    def getBoardAtPly(self, ply, variation=0):
        # Losing on time in FICS game will undo our last move if it was taken
        # too late
        if variation == 0 and ply > self.ply:
            ply = self.ply
        try:
            return self.variations[variation][self._plyToIndex(ply)]
        except IndexError:
            log.error("%d\t%d\t%d\t%d\t%d" % (self.lowply, ply, self.ply,
                                              variation, len(self.variations)))
            raise

    def getMoveAtPly(self, ply, variation=0):
        try:
            return Move(self.variations[variation][self._plyToIndex(ply) +
                                                   1].board.lastMove)
        except IndexError:
            log.error("%d\t%d\t%d\t%d\t%d" % (self.lowply, ply, self.ply,
                                              variation, len(self.variations)))
            raise

    def hasLocalPlayer(self):
        if self.players[0].__type__ == LOCAL or self.players[
                1].__type__ == LOCAL:
            return True
        else:
            return False

    def hasEnginePlayer(self):
        if self.players[0].__type__ == ARTIFICIAL or self.players[
                1].__type__ == ARTIFICIAL:
            return True
        else:
            return False

    def isLocalGame(self):
        if self.players[0].__type__ != REMOTE and self.players[
                1].__type__ != REMOTE:
            return True
        else:
            return False

    def isObservationGame(self):
        return not self.hasLocalPlayer()

    def isEngine2EngineGame(self):
        if self.players[0].__type__ == ARTIFICIAL and self.players[
                1].__type__ == ARTIFICIAL:
            return True
        else:
            return False

    def isPlayingICSGame(self):
        if self.players and self.status in (WAITING_TO_START, PAUSED, RUNNING):
            if self.players[0].__type__ == LOCAL and self.players[1].__type__ == REMOTE or \
               self.players[1].__type__ == LOCAL and self.players[0].__type__ == REMOTE:
                return True
        return False

    def isLoadedGame(self):
        return self.gameno is not None

    # Offer management

    def offerReceived(self, player, offer):
        log.debug("GameModel.offerReceived: offerer=%s %s" %
                  (repr(player), offer))
        if player == self.players[WHITE]:
            opPlayer = self.players[BLACK]
        elif player == self.players[BLACK]:
            opPlayer = self.players[WHITE]
        else:
            # Player comments echoed to opponent if the player started a conversation
            # with you prior to observing a game the player is in #1113
            return

        if offer.type == HURRY_ACTION:
            opPlayer.hurry()

        elif offer.type == CHAT_ACTION:
            # print("GameModel.offerreceived(player, offer)", player.name, offer.param)
            opPlayer.putMessage(offer.param)

        elif offer.type == RESIGNATION:
            if player == self.players[WHITE]:
                self.end(BLACKWON, WON_RESIGN)
            else:
                self.end(WHITEWON, WON_RESIGN)

        elif offer.type == FLAG_CALL:
            assert self.timed
            if self.timemodel.getPlayerTime(1 - player.color) <= 0:
                if self.timemodel.getPlayerTime(player.color) <= 0:
                    self.end(DRAW, DRAW_CALLFLAG)
                elif not playerHasMatingMaterial(self.boards[-1],
                                                 player.color):
                    if player.color == WHITE:
                        self.end(DRAW, DRAW_WHITEINSUFFICIENTANDBLACKTIME)
                    else:
                        self.end(DRAW, DRAW_BLACKINSUFFICIENTANDWHITETIME)
                else:
                    if player == self.players[WHITE]:
                        self.end(WHITEWON, WON_CALLFLAG)
                    else:
                        self.end(BLACKWON, WON_CALLFLAG)
            else:
                player.offerError(offer, ACTION_ERROR_NOT_OUT_OF_TIME)

        elif offer.type == DRAW_OFFER and isClaimableDraw(self.boards[-1]):
            reason = getStatus(self.boards[-1])[1]
            self.end(DRAW, reason)

        elif offer.type == TAKEBACK_OFFER and offer.param < self.lowply:
            player.offerError(offer, ACTION_ERROR_TOO_LARGE_UNDO)

        elif offer.type in OFFERS:
            if offer not in self.offers:
                log.debug("GameModel.offerReceived: doing %s.offer(%s)" % (
                    repr(opPlayer), offer))
                self.offers[offer] = player
                opPlayer.offer(offer)
            # If we updated an older offer, we want to delete the old one
            keys = self.offers.keys()
            for offer_ in keys:
                if offer.type == offer_.type and offer != offer_:
                    del self.offers[offer_]

    def withdrawReceived(self, player, offer):
        log.debug("GameModel.withdrawReceived: withdrawer=%s %s" % (
            repr(player), offer))
        if player == self.players[WHITE]:
            opPlayer = self.players[BLACK]
        else:
            opPlayer = self.players[WHITE]

        if offer in self.offers and self.offers[offer] == player:
            del self.offers[offer]
            opPlayer.offerWithdrawn(offer)
        else:
            player.offerError(offer, ACTION_ERROR_NONE_TO_WITHDRAW)

    def declineReceived(self, player, offer):
        log.debug("GameModel.declineReceived: decliner=%s %s" % (
                  repr(player), offer))
        if player == self.players[WHITE]:
            opPlayer = self.players[BLACK]
        else:
            opPlayer = self.players[WHITE]

        if offer in self.offers and self.offers[offer] == opPlayer:
            del self.offers[offer]
            log.debug("GameModel.declineReceived: declining %s" % offer)
            opPlayer.offerDeclined(offer)
        else:
            player.offerError(offer, ACTION_ERROR_NONE_TO_DECLINE)

    def acceptReceived(self, player, offer):
        log.debug("GameModel.acceptReceived: accepter=%s %s" % (
                  repr(player), offer))
        if player == self.players[WHITE]:
            opPlayer = self.players[BLACK]
        else:
            opPlayer = self.players[WHITE]

        if offer in self.offers and self.offers[offer] == opPlayer:
            if offer.type == DRAW_OFFER:
                self.end(DRAW, DRAW_AGREE)
            elif offer.type == TAKEBACK_OFFER:
                log.debug("GameModel.acceptReceived: undoMoves(%s)" % (
                    self.ply - offer.param))
                self.undoMoves(self.ply - offer.param)
            elif offer.type == ADJOURN_OFFER:
                self.end(ADJOURNED, ADJOURNED_AGREEMENT)
            elif offer.type == ABORT_OFFER:
                self.end(ABORTED, ABORTED_AGREEMENT)
            elif offer.type == PAUSE_OFFER:
                self.pause()
            elif offer.type == RESUME_OFFER:
                self.resume()
            del self.offers[offer]
        else:
            player.offerError(offer, ACTION_ERROR_NONE_TO_ACCEPT)

    # Data stuff

    def loadAndStart(self, uri, loader, gameno, position, first_time=True):
        if first_time:
            assert self.status == WAITING_TO_START

        uriIsFile = not isinstance(uri, str)
        if not uriIsFile:
            chessfile = loader.load(protoopen(uri))
        else:
            chessfile = loader.load(uri)

        self.gameno = gameno
        self.emit("game_loading", uri)
        try:
            chessfile.loadToModel(gameno, -1, self)
        # Postpone error raising to make games loadable to the point of the
        # error
        except LoadingError as e:
            error = e
        else:
            error = None
        if self.players:
            self.players[WHITE].setName(self.tags["White"])
            self.players[BLACK].setName(self.tags["Black"])
        self.emit("game_loaded", uri)

        self.needsSave = False
        if not uriIsFile:
            self.uri = uri
        else:
            self.uri = None

        # Even if the game "starts ended", the players should still be moved
        # to the last position, so analysis is correct, and a possible "undo"
        # will work as expected.
        for spectator in self.spectators.values():
            spectator.setOptionInitialBoard(self)
        for player in self.players:
            player.setOptionInitialBoard(self)
        if self.timed:
            self.timemodel.setMovingColor(self.boards[-1].color)

        if first_time:
            if self.status == RUNNING:
                if self.timed:
                    self.timemodel.start()

            # Store end status from Result tag
            if self.status in (DRAW, WHITEWON, BLACKWON):
                self.endstatus = self.status
            self.status = WAITING_TO_START
            self.start()

        if error:
            raise error

    def save(self, uri, saver, append, position=None):
        if isinstance(uri, basestring):
            fileobj = protosave(uri, append)
            self.uri = uri
        else:
            fileobj = uri
            self.uri = None
        saver.save(fileobj, self, position)
        self.needsSave = False
        self.emit("game_saved", uri)

    # Run stuff

    def run(self):
        log.debug("GameModel.run: Starting. self=%s" % self)
        # Avoid racecondition when self.start is called while we are in
        # self.end
        if self.status != WAITING_TO_START:
            return

        if not self.isLocalGame():
            self.timemodel.handle_gain = False

        self.status = RUNNING

        for player in self.players + list(self.spectators.values()):
            player.start()

        log.debug("GameModel.run: emitting 'game_started' self=%s" % self)
        self.emit("game_started")

        # Let GameModel end() itself on games started with loadAndStart()
        self.checkStatus()

        self.curColor = self.boards[-1].color

        while self.status in (PAUSED, RUNNING, DRAW, WHITEWON, BLACKWON):
            curPlayer = self.players[self.curColor]

            if self.timed:
                log.debug("GameModel.run: id=%s, players=%s, self.ply=%s: updating %s's time" % (
                    id(self), str(self.players), str(self.ply), str(curPlayer)))
                curPlayer.updateTime(
                    self.timemodel.getPlayerTime(self.curColor),
                    self.timemodel.getPlayerTime(1 - self.curColor))

            try:
                log.debug("GameModel.run: id=%s, players=%s, self.ply=%s: calling %s.makeMove()" % (
                    id(self), str(self.players), self.ply, str(curPlayer)))
                if self.ply > self.lowply:
                    move = curPlayer.makeMove(self.boards[-1], self.moves[-1],
                                              self.boards[-2])
                else:
                    move = curPlayer.makeMove(self.boards[-1], None, None)
                log.debug("GameModel.run: id=%s, players=%s, self.ply=%s: got move=%s from %s" % (
                    id(self), str(self.players), self.ply, move, str(curPlayer)))
            except PlayerIsDead as e:
                if self.status in (WAITING_TO_START, PAUSED, RUNNING):
                    stringio = StringIO()
                    traceback.print_exc(file=stringio)
                    error = stringio.getvalue()
                    log.error(
                        "GameModel.run: A Player died: player=%s error=%s\n%s"
                        % (curPlayer, error, e))
                    if self.curColor == WHITE:
                        self.kill(WHITE_ENGINE_DIED)
                    else:
                        self.kill(BLACK_ENGINE_DIED)
                break
            except InvalidMove as e:
                if self.curColor == WHITE:
                    self.end(BLACKWON, WON_ADJUDICATION)
                else:
                    self.end(WHITEWON, WON_ADJUDICATION)
                break
            except TurnInterrupt:
                log.debug("GameModel.run: id=%s, players=%s, self.ply=%s: TurnInterrupt" % (
                    id(self), str(self.players), self.ply))
                self.curColor = self.boards[-1].color
                continue

            log.debug("GameModel.run: id=%s, players=%s, self.ply=%s: acquiring self.applyingMoveLock" % (
                id(self), str(self.players), self.ply))
            assert isinstance(move, Move), "%s" % repr(move)

            self.applyingMoveLock.acquire()
            try:
                log.debug("GameModel.run: id=%s, players=%s, self.ply=%s: applying move=%s" % (
                    id(self), str(self.players), self.ply, str(move)))
                self.needsSave = True
                newBoard = self.boards[-1].move(move)
                newBoard.board.prev = self.boards[-1].board

                # Variation on next move can exist from the hint panel...
                if self.boards[-1].board.next is not None:
                    newBoard.board.children = self.boards[
                        -1].board.next.children

                self.boards = self.variations[0]
                self.boards[-1].board.next = newBoard.board
                self.boards.append(newBoard)
                self.moves.append(move)

                if self.timed:
                    self.timemodel.tap()

                if not self.terminated:
                    self.emit("game_changed", self.ply)

                for spectator in self.spectators.values():
                    if spectator.board == self.boards[-2]:
                        spectator.putMove(self.boards[-1], self.moves[-1],
                                          self.boards[-2])

                self.setOpening()

                self.checkStatus()
                self.curColor = 1 - self.curColor

            finally:
                log.debug("GameModel.run: releasing self.applyingMoveLock")
                self.applyingMoveLock.release()

    def checkStatus(self):
        """ Updates self.status so it fits with what getStatus(boards[-1])
            would return. That is, if the game is e.g. check mated this will
            call mode.end(), or if moves have been undone from an otherwise
            ended position, this will call __resume and emit game_unended. """

        log.debug("GameModel.checkStatus:")

        # call flag by engine
        if self.isEngine2EngineGame() and self.status in UNDOABLE_STATES:
            return

        status, reason = getStatus(self.boards[-1])

        if self.endstatus is not None:
            self.end(self.endstatus, reason)
            return

        if status != RUNNING and self.status in (WAITING_TO_START, PAUSED,
                                                 RUNNING):
            if status == DRAW and reason in (DRAW_REPITITION, DRAW_50MOVES):
                if self.isEngine2EngineGame():
                    self.end(status, reason)
                    return
            else:
                self.end(status, reason)
                return

        if status != self.status and self.status in UNDOABLE_STATES \
                and self.reason in UNDOABLE_REASONS:
            self.__resume()
            self.status = status
            self.reason = UNKNOWN_REASON
            self.emit("game_unended")

    def __pause(self):
        log.debug("GameModel.__pause: %s" % self)
        if self.isEngine2EngineGame():
            for player in self.players:
                player.end(self.status, self.reason)
            if self.timed:
                self.timemodel.end()
        else:
            for player in self.players:
                player.pause()
            if self.timed:
                self.timemodel.pause()

    @inthread
    def pause(self):
        """ Players will raise NotImplementedError if they doesn't support
            pause. Spectators will be ignored. """

        self.applyingMoveLock.acquire()
        try:
            self.__pause()
            self.status = PAUSED
        finally:
            self.applyingMoveLock.release()
        self.emit("game_paused")

    def __resume(self):
        for player in self.players:
            player.resume()
        if self.timed:
            self.timemodel.resume()
        self.emit("game_resumed")

    @inthread
    def resume(self):
        self.applyingMoveLock.acquire()
        try:
            self.status = RUNNING
            self.__resume()
        finally:
            self.applyingMoveLock.release()

    def end(self, status, reason):
        if self.status not in UNFINISHED_STATES:
            log.info(
                "GameModel.end: Can't end a game that's already ended: %s %s" %
                (status, reason))
            return
        if self.status not in (WAITING_TO_START, PAUSED, RUNNING):
            self.needsSave = True

        log.debug("GameModel.end: players=%s, self.ply=%s: Ending a game with status %d for reason %d" % (
            repr(self.players), str(self.ply), status, reason))
        self.status = status
        self.reason = reason

        self.emit("game_ended", reason)

        self.__pause()

    def kill(self, reason):
        log.debug("GameModel.kill: players=%s, self.ply=%s: Killing a game for reason %d\n%s" % (
                  repr(self.players), str(self.ply), reason, "".join(
                      traceback.format_list(traceback.extract_stack())).strip()))

        self.status = KILLED
        self.reason = reason

        for player in self.players:
            player.end(self.status, reason)

        for spectator in self.spectators.values():
            spectator.end(self.status, reason)

        if self.timed:
            self.timemodel.end()

        self.emit("game_ended", reason)

    def terminate(self):
        log.debug("GameModel.terminate: %s" % self)
        self.terminated = True

        if self.status != KILLED:
            for player in self.players:
                player.end(self.status, self.reason)

            analyzer_types = list(self.spectators.keys())
            for analyzer_type in analyzer_types:
                self.remove_analyzer(analyzer_type)

            if self.timed:
                log.debug("GameModel.terminate: -> timemodel.end()")
                self.timemodel.end()
                log.debug("GameModel.terminate: <- timemodel.end() %s" %
                          repr(self.timemodel))
                self.timemodel.disconnect(self.zero_reached_cid)

        # ICGameModel may did this if game was a FICS game
        if self.connections is not None:
            for player in self.players:
                for cid in self.connections[player]:
                    player.disconnect(cid)
        self.connections = {}

        self.timemodel.gamemodel = None
        self.players = []
        self.emit("game_terminated")

    # Other stuff

    @inthread
    @undolocked
    def undoMoves(self, moves):
        """ Undo and remove moves number of moves from the game history from
            the GameModel, players, and any spectators """
        if self.ply < 1 or moves < 1:
            return
        if self.ply - moves < 0:
            # There is no way in the current threaded/asynchronous design
            # for the GUI to know that the number of moves it requests to takeback
            # will still be valid once the undo is actually processed. So, until
            # we either add some locking or get a synchronous design, we quietly
            # "fix" the takeback request rather than cause AssertionError or IndexError
            moves = 1

        log.debug("GameModel.undoMoves: players=%s, self.ply=%s, moves=%s, board=%s" % (
                  repr(self.players), self.ply, moves, self.boards[-1]))
        log.debug("GameModel.undoMoves: acquiring self.applyingMoveLock")
        self.applyingMoveLock.acquire()
        log.debug("GameModel.undoMoves: self.applyingMoveLock acquired")
        try:
            self.emit("moves_undoing", moves)
            self.needsSave = True

            self.boards = self.variations[0]
            del self.boards[-moves:]
            del self.moves[-moves:]
            self.boards[-1].board.next = None

            for player in self.players:
                player.playerUndoMoves(moves, self)
            for spectator in self.spectators.values():
                spectator.spectatorUndoMoves(moves, self)

            log.debug("GameModel.undoMoves: undoing timemodel")
            if self.timed:
                self.timemodel.undoMoves(moves)

            self.checkStatus()
            self.setOpening()
        finally:
            log.debug("GameModel.undoMoves: releasing self.applyingMoveLock")
            self.applyingMoveLock.release()

        self.emit("moves_undone", moves)

    def isChanged(self):
        if self.ply == 0:
            return False
        if self.needsSave:
            return True
        if not self.uri or not isWriteable(self.uri):
            return True
        return False

    def add_variation(self, board, moves, comment="", score=""):
        board0 = board
        board = board0.clone()
        board.board.prev = None

        variation = [board]

        for move in moves:
            new = board.move(move)
            if len(variation) == 1:
                new.board.prev = board0.board
                variation[0].board.next = new.board
            else:
                new.board.prev = board.board
                board.board.next = new.board
            variation.append(new)
            board = new

        if board0.board.next is None:
            # If we are in the latest played board, and want to add a variation
            # we have to add a not played yet board first
            # which can hold the variation as his child
            from pychess.Utils.lutils.LBoard import LBoard
            null_board = LBoard()
            null_board.prev = board0.board
            board0.board.next = null_board

        board0.board.next.children.append(
            [vboard.board for vboard in variation])

        head = None
        for vari in self.variations:
            if board0 in vari:
                head = vari
                break

        variation[0] = board0
        self.variations.append(head[:board0.ply - self.lowply] + variation)
        self.needsSave = True
        self.emit("variation_added", board0.board.next.children[-1],
                  board0.board.next, comment, score)
        return self.variations[-1]

    def add_move2variation(self, board, move, variationIdx):
        new = board.move(move)
        new.board.prev = board.board
        board.board.next = new.board

        # Find the variation (low level lboard list) to append
        cur_board = board.board
        vari = None
        while cur_board.prev is not None:
            for child in cur_board.prev.next.children:
                if isinstance(child, list) and cur_board in child:
                    vari = child
                    break
            if vari is None:
                cur_board = cur_board.prev
            else:
                break
        vari.append(new.board)

        self.variations[variationIdx].append(new)
        self.needsSave = True
        self.emit("variation_extended", board.board, new.board)
Example #30
0
        def onResponse(dialog, response):
            if response == COPY:
                clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
                clipboard.set_text(cls.get_fen(), -1)
                # print("put clipboard:", clipboard.wait_for_text())
                return
            elif response == CLEAR:
                cls.board_control.emit("action", "SETUP", True)
                cls.ini_widgets(True)
                # print("clear")
                return
            elif response == PASTE:
                clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
                text = clipboard.wait_for_text()
                # print("got clipboard:", text)
                if text is None or len(text.split()) < 2:
                    return
                try:
                    lboard = cls.setupmodel.variant(setup=text).board
                    cls.ini_widgets(lboard.asFen())
                    cls.board_control.emit("action", "SETUP", text)
                except SyntaxError as e:
                    d = Gtk.MessageDialog(mainwindow(),
                                          type=Gtk.MessageType.WARNING,
                                          buttons=Gtk.ButtonsType.OK,
                                          message_format=e.args[0])
                    if len(e.args) > 1:
                        d.format_secondary_text(e.args[1])
                    d.connect("response", lambda d, a: d.hide())
                    d.show()
                return
            elif response == INITIAL:
                lboard = cls.setupmodel.variant(setup=FEN_START).board
                cls.ini_widgets(lboard.asFen())
                cls.board_control.emit("action", "SETUP", FEN_START)
                return
            elif response != Gtk.ResponseType.OK:
                cls.widgets["newgamedialog"].hide()
                cls.widgets["newgamedialog"].disconnect(handlerId)
                return

            if hasattr(cls, "board_control"):
                cls.board_control.emit("action", "CLOSE", None)

            # Find variant
            if cls.widgets["playNormalRadio"].get_active():
                variant_index = NORMALCHESS
            elif cls.widgets["playVariant1Radio"].get_active():
                variant_index = conf.get("ngvariant1", FISCHERRANDOMCHESS)
            else:
                variant_index = conf.get("ngvariant2", LOSERSCHESS)
            variant = variants[variant_index]

            # Find time
            if cls.widgets["notimeRadio"].get_active():
                secs = 0
                incr = 0
                moves = 0
            elif cls.widgets["blitzRadio"].get_active():
                secs = cls.ngblitz_min.get_value_as_int() * 60
                incr = cls.ngblitz_gain.get_value_as_int()
                moves = 0
            elif cls.widgets["rapidRadio"].get_active():
                secs = cls.ngrapid_min.get_value_as_int() * 60
                incr = cls.ngrapid_gain.get_value_as_int()
                moves = 0
            elif cls.widgets["normalRadio"].get_active():
                secs = cls.ngnormal_min.get_value_as_int() * 60
                incr = cls.ngnormal_gain.get_value_as_int()
                moves = 0
            elif cls.widgets["classicalRadio"].get_active():
                secs = cls.ngclassical_min.get_value_as_int() * 60
                incr = 0
                moves = cls.ngclassical_moves.get_value_as_int()

            # Find players
            player0combo = cls.widgets["whitePlayerCombobox"]
            player0 = player0combo.get_active()

            tree_iter = player0combo.get_active_iter()
            if tree_iter is not None:
                model = player0combo.get_model()
                name0 = model[tree_iter][1]

            diffi0 = int(cls.widgets["skillSlider1"].get_value())

            player1combo = cls.widgets["blackPlayerCombobox"]
            player1 = player1combo.get_active()

            tree_iter = player1combo.get_active_iter()
            if tree_iter is not None:
                model = player1combo.get_model()
                name1 = model[tree_iter][1]

            diffi1 = int(cls.widgets["skillSlider2"].get_value())

            # Prepare players
            playertups = []
            for i, playerno, name, diffi, color in ((0, player0, name0, diffi0,
                                                     WHITE),
                                                    (1, player1, name1, diffi1,
                                                     BLACK)):
                if playerno > 0:
                    engine = discoverer.getEngineByName(name)
                    playertups.append(
                        (ARTIFICIAL, discoverer.initPlayerEngine,
                         [engine, color, diffi, variant, secs, incr,
                          moves], name))
                else:
                    if not playertups or playertups[0][0] != LOCAL:
                        name = conf.get("firstName", _("You"))
                    else:
                        name = conf.get("secondName", _("Guest"))
                    playertups.append((LOCAL, Human, (color, name), name))

            # Set forcePonderOff initPlayerEngine param True in engine-engine games
            if playertups[0][0] == ARTIFICIAL and playertups[1][
                    0] == ARTIFICIAL:
                playertups[0][2].append(True)
                playertups[1][2].append(True)

            timemodel = TimeModel(secs, incr, moves=moves)
            gamemodel = GameModel(timemodel, variant)

            if not validate(gamemodel):
                return
            else:
                cls.widgets["newgamedialog"].hide()
                cls.widgets["newgamedialog"].disconnect(handlerId)
                callback(gamemodel, playertups[0], playertups[1])
Example #31
0
class GameModel(GObject.GObject, Thread):
    """ GameModel contains all available data on a chessgame.
        It also has the task of controlling players actions and moves """

    __gsignals__ = {
        # game_started is emitted when control is given to the players for the
        # first time. Notice this is after players.start has been called.
        "game_started": (GObject.SignalFlags.RUN_FIRST, None, ()),
        # game_changed is emitted when a move has been made.
        "game_changed": (GObject.SignalFlags.RUN_FIRST, None, (int, )),
        # moves_undoig is emitted when a undoMoves call has been accepted, but
        # before anywork has been done to execute it.
        "moves_undoing": (GObject.SignalFlags.RUN_FIRST, None, (int, )),
        # moves_undone is emitted after n moves have been undone in the
        # gamemodel and the players.
        "moves_undone": (GObject.SignalFlags.RUN_FIRST, None, (int, )),
        # game_unended is emitted if moves have been undone, such that the game
        # which had previously ended, is now again active.
        "game_unended": (GObject.SignalFlags.RUN_FIRST, None, ()),
        # game_loading is emitted if the GameModel is about to load in a chess
        # game from a file.
        "game_loading": (GObject.SignalFlags.RUN_FIRST, None, (object, )),
        # game_loaded is emitted after the chessformat handler has loaded in
        # all the moves from a file to the game model.
        "game_loaded": (GObject.SignalFlags.RUN_FIRST, None, (object, )),
        # game_saved is emitted in the end of model.save()
        "game_saved": (GObject.SignalFlags.RUN_FIRST, None, (str, )),
        # game_ended is emitted if the models state has been changed to an
        # "ended state"
        "game_ended": (GObject.SignalFlags.RUN_FIRST, None, (int, )),
        # game_terminated is emitted if the game was terminated. That is all
        # players and clocks were stopped, and it is no longer possible to
        # resume the game, even by undo.
        "game_terminated": (GObject.SignalFlags.RUN_FIRST, None, ()),
        # game_paused is emitted if the game was successfully paused.
        "game_paused": (GObject.SignalFlags.RUN_FIRST, None, ()),
        # game_paused is emitted if the game was successfully resumed from a
        # pause.
        "game_resumed": (GObject.SignalFlags.RUN_FIRST, None, ()),
        # action_error is currently only emitted by ICGameModel, in the case
        # the "web model" didn't accept the action you were trying to do.
        "action_error": (GObject.SignalFlags.RUN_FIRST, None, (object, int)),
        # players_changed is emitted if the players list was changed.
        "players_changed": (GObject.SignalFlags.RUN_FIRST, None, ()),
        "analyzer_added": (GObject.SignalFlags.RUN_FIRST, None, (object, str)),
        "analyzer_removed": (GObject.SignalFlags.RUN_FIRST, None,
                             (object, str)),
        "analyzer_paused": (GObject.SignalFlags.RUN_FIRST, None,
                            (object, str)),
        "analyzer_resumed": (GObject.SignalFlags.RUN_FIRST, None,
                             (object, str)),
        # opening_changed is emitted if the move changed the opening.
        "opening_changed": (GObject.SignalFlags.RUN_FIRST, None, ()),
        # variation_added is emitted if a variation was added.
        "variation_added": (GObject.SignalFlags.RUN_FIRST, None,
                            (object, object)),
        # variation_extended is emitted if a new move was added to a variation.
        "variation_extended": (GObject.SignalFlags.RUN_FIRST, None,
                               (object, object)),
        # scores_changed is emitted if the analyzing scores was changed.
        "analysis_changed": (GObject.SignalFlags.RUN_FIRST, None, (int, )),
        # analysis_finished is emitted if the game analyzing finished stepping on all moves.
        "analysis_finished": (GObject.SignalFlags.RUN_FIRST, None, ()),
        # FICS games can get kibitz/whisper messages
        "message_received": (GObject.SignalFlags.RUN_FIRST, None, (str, str)),
        # FICS games can have observers
        "observers_received": (GObject.SignalFlags.RUN_FIRST, None, (str, )),
    }

    def __init__(self, timemodel=None, variant=NormalBoard):
        GObject.GObject.__init__(self)
        Thread.__init__(self, name=fident(self.run))
        self.daemon = True
        self.variant = variant
        self.boards = [variant(setup=True)]

        self.moves = []
        self.scores = {}
        self.spy_scores = {}
        self.players = []

        self.gameno = None
        self.variations = [self.boards]

        self.terminated = False
        self.status = WAITING_TO_START
        self.reason = UNKNOWN_REASON
        self.curColor = WHITE

        if timemodel is None:
            self.timemodel = TimeModel()
        else:
            self.timemodel = timemodel
        self.timemodel.gamemodel = self

        self.connections = defaultdict(list)  # mainly for IC subclasses
        self.analyzer_cids = {}
        self.examined = False

        now = datetime.datetime.now()
        self.tags = {
            "Event": _("Local Event"),
            "Site": _("Local Site"),
            "Round": 1,
            "Year": now.year,
            "Month": now.month,
            "Day": now.day,
            "Time": "%02d:%02d:00" % (now.hour, now.minute),
            "Result": "*",
        }

        self.endstatus = None
        self.zero_reached_cid = None

        self.timed = self.timemodel.minutes != 0 or self.timemodel.gain != 0
        if self.timed:
            self.zero_reached_cid = self.timemodel.connect('zero_reached', self.zero_reached)

            self.tags["TimeControl"] = \
                "%d+%d" % (self.timemodel.minutes * 60, self.timemodel.gain)
            # Notice: tags["WhiteClock"] and tags["BlackClock"] are never set
            # on the gamemodel, but simply written or read during saving/
            # loading from pgn. If you want to know the time left for a player,
            # check the time model.

        # Keeps track of offers, so that accepts can be spotted
        self.offers = {}

        # True if the game has been changed since last save
        self.needsSave = False

        # The uri the current game was loaded from, or None if not a loaded game
        self.uri = None

        # Link to additiona info
        self.info = None

        self.spectators = {}

        self.applyingMoveLock = RLock()
        self.undoLock = RLock()
        self.undoQueue = Queue()

    def zero_reached(self, timemodel, color):
        if conf.get('autoCallFlag', False) and self.players[1 - color].__type__ == ARTIFICIAL:
            if self.status == RUNNING and timemodel.getPlayerTime(color) <= 0:
                log.info(
                    'Automatically sending flag call on behalf of player %s.' %
                    self.players[1 - color].name)
                self.players[1 - color].emit("offer", Offer(FLAG_CALL))

    def __repr__(self):
        string = "<GameModel at %s" % id(self)
        string += " (ply=%s" % self.ply
        if len(self.moves) > 0:
            string += ", move=%s" % self.moves[-1]
        string += ", variant=%s" % self.variant.name.encode('utf-8')
        string += ", status=%s, reason=%s" % (str(self.status), str(self.reason))
        string += ", players=%s" % str(self.players)
        string += ", tags=%s" % str(self.tags)
        if len(self.boards) > 0:
            string += "\nboard=%s" % self.boards[-1]
        return string + ")>"

    @property
    def display_text(self):
        if self.variant == NormalBoard and not self.timed:
            return "[ " + _("Untimed") + " ]"
        else:
            text = "[ "
            if self.variant != NormalBoard:
                text += self.variant.name + " "
            if self.timed:
                text += self.timemodel.display_text + " "
            return text + "]"

    def setPlayers(self, players):
        log.debug("GameModel.setPlayers: starting")
        assert self.status == WAITING_TO_START
        self.players = players
        for player in self.players:
            self.connections[player].append(player.connect("offer",
                                                           self.offerReceived))
            self.connections[player].append(player.connect(
                "withdraw", self.withdrawReceived))
            self.connections[player].append(player.connect(
                "decline", self.declineReceived))
            self.connections[player].append(player.connect(
                "accept", self.acceptReceived))
        self.tags["White"] = str(self.players[WHITE])
        self.tags["Black"] = str(self.players[BLACK])
        log.debug("GameModel.setPlayers: -> emit players_changed")
        self.emit("players_changed")
        log.debug("GameModel.setPlayers: <- emit players_changed")
        log.debug("GameModel.setPlayers: returning")

    def color(self, player):
        if player is self.players[0]:
            return WHITE
        else:
            return BLACK

    def start_analyzer(self, analyzer_type):
        from pychess.Players.engineNest import init_engine
        analyzer = init_engine(analyzer_type, self)
        if analyzer is None:
            return

        analyzer.setOptionInitialBoard(self)
        self.spectators[analyzer_type] = analyzer
        self.emit("analyzer_added", analyzer, analyzer_type)
        self.analyzer_cids[analyzer_type] = analyzer.connect("analyze", self.on_analyze)
        return analyzer

    def remove_analyzer(self, analyzer_type):
        try:
            analyzer = self.spectators[analyzer_type]
        except KeyError:
            return

        analyzer.disconnect(self.analyzer_cids[analyzer_type])
        analyzer.end(KILLED, UNKNOWN_REASON)
        self.emit("analyzer_removed", analyzer, analyzer_type)
        del self.spectators[analyzer_type]

    def resume_analyzer(self, analyzer_type):
        try:
            analyzer = self.spectators[analyzer_type]
        except KeyError:
            analyzer = self.start_analyzer(analyzer_type)
            if analyzer is None:
                return

        analyzer.resume()
        analyzer.setOptionInitialBoard(self)
        self.emit("analyzer_resumed", analyzer, analyzer_type)

    def pause_analyzer(self, analyzer_type):
        try:
            analyzer = self.spectators[analyzer_type]
        except KeyError:
            return

        analyzer.pause()
        self.emit("analyzer_paused", analyzer, analyzer_type)

    def restart_analyzer(self, analyzer_type):
        self.remove_analyzer(analyzer_type)
        self.start_analyzer(analyzer_type)

    def on_analyze(self, analyzer, analysis):
        if analysis and analysis[0] is not None:
            pv, score, depth = analysis[0]
            ply = analyzer.board.ply
            if score is not None:
                if analyzer.mode == ANALYZING:
                    self.scores[ply] = (pv, score, depth)
                    self.emit("analysis_changed", ply)
                else:
                    self.spy_scores[ply] = (pv, score, depth)

    def setOpening(self, ply=None):
        if ply is None:
            ply = self.ply
        if ply > 40:
            return

        if ply > 0:
            opening = get_eco(self.getBoardAtPly(ply).board.hash)
        else:
            opening = ("", "", "")
        if opening is not None:
            self.tags["ECO"] = opening[0]
            self.tags["Opening"] = opening[1]
            self.tags["Variation"] = opening[2]
            self.emit("opening_changed")

    # Board stuff

    def _get_ply(self):
        return self.boards[-1].ply

    ply = property(_get_ply)

    def _get_lowest_ply(self):
        return self.boards[0].ply

    lowply = property(_get_lowest_ply)

    def _get_curplayer(self):
        try:
            return self.players[self.getBoardAtPly(self.ply).color]
        except IndexError:
            log.error("%s %s" %
                      (self.players, self.getBoardAtPly(self.ply).color))
            raise

    curplayer = property(_get_curplayer)

    def _get_waitingplayer(self):
        try:
            return self.players[1 - self.getBoardAtPly(self.ply).color]
        except IndexError:
            log.error("%s %s" %
                      (self.players, 1 - self.getBoardAtPly(self.ply).color))
            raise

    waitingplayer = property(_get_waitingplayer)

    def _plyToIndex(self, ply):
        index = ply - self.lowply
        if index < 0:
            raise IndexError("%s < %s\n" % (ply, self.lowply))
        return index

    def getBoardAtPly(self, ply, variation=0):
        # Losing on time in FICS game will undo our last move if it was taken
        # too late
        if variation == 0 and ply > self.ply:
            ply = self.ply
        try:
            return self.variations[variation][self._plyToIndex(ply)]
        except IndexError:
            log.error("%d\t%d\t%d\t%d\t%d" % (self.lowply, ply, self.ply,
                                              variation, len(self.variations)))
            raise

    def getMoveAtPly(self, ply, variation=0):
        try:
            return Move(self.variations[variation][self._plyToIndex(ply) +
                                                   1].board.lastMove)
        except IndexError:
            log.error("%d\t%d\t%d\t%d\t%d" % (self.lowply, ply, self.ply,
                                              variation, len(self.variations)))
            raise

    def hasLocalPlayer(self):
        if self.players[0].__type__ == LOCAL or self.players[
                1].__type__ == LOCAL:
            return True
        else:
            return False

    def hasEnginePlayer(self):
        if self.players[0].__type__ == ARTIFICIAL or self.players[
                1].__type__ == ARTIFICIAL:
            return True
        else:
            return False

    def isLocalGame(self):
        if self.players[0].__type__ != REMOTE and self.players[
                1].__type__ != REMOTE:
            return True
        else:
            return False

    def isObservationGame(self):
        return not self.hasLocalPlayer()

    def isEngine2EngineGame(self):
        if self.players[0].__type__ == ARTIFICIAL and self.players[
                1].__type__ == ARTIFICIAL:
            return True
        else:
            return False

    def isPlayingICSGame(self):
        if self.players and self.status in (WAITING_TO_START, PAUSED, RUNNING):
            if (self.players[0].__type__ == LOCAL and self.players[1].__type__ == REMOTE) or \
               (self.players[1].__type__ == LOCAL and self.players[0].__type__ == REMOTE) or \
               (self.players[1].__type__ == REMOTE and self.players[0].__type__ == REMOTE and self.examined):
                return True
        return False

    def isLoadedGame(self):
        return self.gameno is not None

    # Offer management

    def offerReceived(self, player, offer):
        log.debug("GameModel.offerReceived: offerer=%s %s" %
                  (repr(player), offer))
        if player == self.players[WHITE]:
            opPlayer = self.players[BLACK]
        elif player == self.players[BLACK]:
            opPlayer = self.players[WHITE]
        else:
            # Player comments echoed to opponent if the player started a conversation
            # with you prior to observing a game the player is in #1113
            return

        if offer.type == HURRY_ACTION:
            opPlayer.hurry()

        elif offer.type == CHAT_ACTION:
            # print("GameModel.offerreceived(player, offer)", player.name, offer.param)
            opPlayer.putMessage(offer.param)

        elif offer.type == RESIGNATION:
            if player == self.players[WHITE]:
                self.end(BLACKWON, WON_RESIGN)
            else:
                self.end(WHITEWON, WON_RESIGN)

        elif offer.type == FLAG_CALL:
            assert self.timed
            if self.timemodel.getPlayerTime(1 - player.color) <= 0:
                if self.timemodel.getPlayerTime(player.color) <= 0:
                    self.end(DRAW, DRAW_CALLFLAG)
                elif not playerHasMatingMaterial(self.boards[-1],
                                                 player.color):
                    if player.color == WHITE:
                        self.end(DRAW, DRAW_WHITEINSUFFICIENTANDBLACKTIME)
                    else:
                        self.end(DRAW, DRAW_BLACKINSUFFICIENTANDWHITETIME)
                else:
                    if player == self.players[WHITE]:
                        self.end(WHITEWON, WON_CALLFLAG)
                    else:
                        self.end(BLACKWON, WON_CALLFLAG)
            else:
                player.offerError(offer, ACTION_ERROR_NOT_OUT_OF_TIME)

        elif offer.type == DRAW_OFFER and isClaimableDraw(self.boards[-1]):
            reason = getStatus(self.boards[-1])[1]
            self.end(DRAW, reason)

        elif offer.type == TAKEBACK_OFFER and offer.param < self.lowply:
            player.offerError(offer, ACTION_ERROR_TOO_LARGE_UNDO)

        elif offer.type in OFFERS:
            if offer not in self.offers:
                log.debug("GameModel.offerReceived: doing %s.offer(%s)" % (
                    repr(opPlayer), offer))
                self.offers[offer] = player
                opPlayer.offer(offer)
            # If we updated an older offer, we want to delete the old one
            keys = self.offers.keys()
            for offer_ in keys:
                if offer.type == offer_.type and offer != offer_:
                    del self.offers[offer_]

    def withdrawReceived(self, player, offer):
        log.debug("GameModel.withdrawReceived: withdrawer=%s %s" % (
            repr(player), offer))
        if player == self.players[WHITE]:
            opPlayer = self.players[BLACK]
        else:
            opPlayer = self.players[WHITE]

        if offer in self.offers and self.offers[offer] == player:
            del self.offers[offer]
            opPlayer.offerWithdrawn(offer)
        else:
            player.offerError(offer, ACTION_ERROR_NONE_TO_WITHDRAW)

    def declineReceived(self, player, offer):
        log.debug("GameModel.declineReceived: decliner=%s %s" % (
                  repr(player), offer))
        if player == self.players[WHITE]:
            opPlayer = self.players[BLACK]
        else:
            opPlayer = self.players[WHITE]

        if offer in self.offers and self.offers[offer] == opPlayer:
            del self.offers[offer]
            log.debug("GameModel.declineReceived: declining %s" % offer)
            opPlayer.offerDeclined(offer)
        else:
            player.offerError(offer, ACTION_ERROR_NONE_TO_DECLINE)

    def acceptReceived(self, player, offer):
        log.debug("GameModel.acceptReceived: accepter=%s %s" % (
                  repr(player), offer))
        if player == self.players[WHITE]:
            opPlayer = self.players[BLACK]
        else:
            opPlayer = self.players[WHITE]

        if offer in self.offers and self.offers[offer] == opPlayer:
            if offer.type == DRAW_OFFER:
                self.end(DRAW, DRAW_AGREE)
            elif offer.type == TAKEBACK_OFFER:
                log.debug("GameModel.acceptReceived: undoMoves(%s)" % (
                    self.ply - offer.param))
                self.undoMoves(self.ply - offer.param)
            elif offer.type == ADJOURN_OFFER:
                self.end(ADJOURNED, ADJOURNED_AGREEMENT)
            elif offer.type == ABORT_OFFER:
                self.end(ABORTED, ABORTED_AGREEMENT)
            elif offer.type == PAUSE_OFFER:
                self.pause()
            elif offer.type == RESUME_OFFER:
                self.resume()
            del self.offers[offer]
        else:
            player.offerError(offer, ACTION_ERROR_NONE_TO_ACCEPT)

    # Data stuff

    def loadAndStart(self, uri, loader, gameno, position, first_time=True):
        if first_time:
            assert self.status == WAITING_TO_START

        uriIsFile = not isinstance(uri, str)
        if not uriIsFile:
            chessfile = loader.load(protoopen(uri))
        else:
            chessfile = loader.load(uri)

        self.gameno = gameno
        self.emit("game_loading", uri)
        try:
            chessfile.loadToModel(gameno, -1, self)
        # Postpone error raising to make games loadable to the point of the
        # error
        except LoadingError as e:
            error = e
        else:
            error = None
        if self.players:
            self.players[WHITE].setName(self.tags["White"])
            self.players[BLACK].setName(self.tags["Black"])
        self.emit("game_loaded", uri)

        self.needsSave = False
        if not uriIsFile:
            self.uri = uri
        else:
            self.uri = None

        # Even if the game "starts ended", the players should still be moved
        # to the last position, so analysis is correct, and a possible "undo"
        # will work as expected.
        for spectator in self.spectators.values():
            spectator.setOptionInitialBoard(self)
        for player in self.players:
            player.setOptionInitialBoard(self)
        if self.timed:
            self.timemodel.setMovingColor(self.boards[-1].color)

        if first_time:
            if self.status == RUNNING:
                if self.timed:
                    self.timemodel.start()

            # Store end status from Result tag
            if self.status in (DRAW, WHITEWON, BLACKWON):
                self.endstatus = self.status
            self.status = WAITING_TO_START
            self.start()

        if error:
            raise error

    def save(self, uri, saver, append, position=None):
        if isinstance(uri, basestring) and not hasattr(saver, "Database"):
            fileobj = protosave(uri, append)
            self.uri = uri
        else:
            fileobj = uri
            self.uri = None
        saver.save(fileobj, self, position)
        self.needsSave = False
        self.emit("game_saved", uri)

    # Run stuff

    def run(self):
        log.debug("GameModel.run: Starting. self=%s" % self)
        # Avoid racecondition when self.start is called while we are in
        # self.end
        if self.status != WAITING_TO_START:
            return

        if not self.isLocalGame():
            self.timemodel.handle_gain = False

        self.status = RUNNING

        for player in self.players + list(self.spectators.values()):
            player.start()

        log.debug("GameModel.run: emitting 'game_started' self=%s" % self)
        self.emit("game_started")

        # Let GameModel end() itself on games started with loadAndStart()
        self.checkStatus()

        self.curColor = self.boards[-1].color

        while self.status in (PAUSED, RUNNING, DRAW, WHITEWON, BLACKWON):
            curPlayer = self.players[self.curColor]

            if self.timed:
                log.debug("GameModel.run: id=%s, players=%s, self.ply=%s: updating %s's time" % (
                    id(self), str(self.players), str(self.ply), str(curPlayer)))
                curPlayer.updateTime(
                    self.timemodel.getPlayerTime(self.curColor),
                    self.timemodel.getPlayerTime(1 - self.curColor))

            try:
                log.debug("GameModel.run: id=%s, players=%s, self.ply=%s: calling %s.makeMove()" % (
                    id(self), str(self.players), self.ply, str(curPlayer)))
                if self.ply > self.lowply:
                    move = curPlayer.makeMove(self.boards[-1], self.moves[-1],
                                              self.boards[-2])
                else:
                    move = curPlayer.makeMove(self.boards[-1], None, None)
                log.debug("GameModel.run: id=%s, players=%s, self.ply=%s: got move=%s from %s" % (
                    id(self), str(self.players), self.ply, move, str(curPlayer)))
            except PlayerIsDead as e:
                if self.status in (WAITING_TO_START, PAUSED, RUNNING):
                    stringio = StringIO()
                    traceback.print_exc(file=stringio)
                    error = stringio.getvalue()
                    log.error(
                        "GameModel.run: A Player died: player=%s error=%s\n%s"
                        % (curPlayer, error, e))
                    if self.curColor == WHITE:
                        self.kill(WHITE_ENGINE_DIED)
                    else:
                        self.kill(BLACK_ENGINE_DIED)
                break
            except InvalidMove as e:
                if self.curColor == WHITE:
                    self.end(BLACKWON, WON_ADJUDICATION)
                else:
                    self.end(WHITEWON, WON_ADJUDICATION)
                break
            except TurnInterrupt:
                log.debug("GameModel.run: id=%s, players=%s, self.ply=%s: TurnInterrupt" % (
                    id(self), str(self.players), self.ply))
                self.curColor = self.boards[-1].color
                continue

            log.debug("GameModel.run: id=%s, players=%s, self.ply=%s: acquiring self.applyingMoveLock" % (
                id(self), str(self.players), self.ply))
            assert isinstance(move, Move), "%s" % repr(move)

            self.applyingMoveLock.acquire()
            try:
                log.debug("GameModel.run: id=%s, players=%s, self.ply=%s: applying move=%s" % (
                    id(self), str(self.players), self.ply, str(move)))
                self.needsSave = True
                newBoard = self.boards[-1].move(move)
                newBoard.board.prev = self.boards[-1].board

                # Variation on next move can exist from the hint panel...
                if self.boards[-1].board.next is not None:
                    newBoard.board.children = self.boards[
                        -1].board.next.children

                self.boards = self.variations[0]
                self.boards[-1].board.next = newBoard.board
                self.boards.append(newBoard)
                self.moves.append(move)

                if self.timed:
                    self.timemodel.tap()

                if not self.terminated:
                    self.emit("game_changed", self.ply)

                for spectator in self.spectators.values():
                    if spectator.board == self.boards[-2]:
                        spectator.putMove(self.boards[-1], self.moves[-1],
                                          self.boards[-2])

                self.setOpening()

                self.checkStatus()
                self.curColor = 1 - self.curColor

            finally:
                log.debug("GameModel.run: releasing self.applyingMoveLock")
                self.applyingMoveLock.release()

    def checkStatus(self):
        """ Updates self.status so it fits with what getStatus(boards[-1])
            would return. That is, if the game is e.g. check mated this will
            call mode.end(), or if moves have been undone from an otherwise
            ended position, this will call __resume and emit game_unended. """

        log.debug("GameModel.checkStatus:")

        # call flag by engine
        if self.isEngine2EngineGame() and self.status in UNDOABLE_STATES:
            return

        status, reason = getStatus(self.boards[-1])

        if self.endstatus is not None:
            self.end(self.endstatus, reason)
            return

        if status != RUNNING and self.status in (WAITING_TO_START, PAUSED,
                                                 RUNNING):
            if status == DRAW and reason in (DRAW_REPITITION, DRAW_50MOVES):
                if self.isEngine2EngineGame():
                    self.end(status, reason)
                    return
            else:
                self.end(status, reason)
                return

        if status != self.status and self.status in UNDOABLE_STATES \
                and self.reason in UNDOABLE_REASONS:
            self.__resume()
            self.status = status
            self.reason = UNKNOWN_REASON
            self.emit("game_unended")

    def __pause(self):
        log.debug("GameModel.__pause: %s" % self)
        if self.isEngine2EngineGame():
            for player in self.players:
                player.end(self.status, self.reason)
            if self.timed:
                self.timemodel.end()
        else:
            for player in self.players:
                player.pause()
            if self.timed:
                self.timemodel.pause()

    @inthread
    def pause(self):
        """ Players will raise NotImplementedError if they doesn't support
            pause. Spectators will be ignored. """

        self.applyingMoveLock.acquire()
        try:
            self.__pause()
            self.status = PAUSED
        finally:
            self.applyingMoveLock.release()
        self.emit("game_paused")

    def __resume(self):
        for player in self.players:
            player.resume()
        if self.timed:
            self.timemodel.resume()
        self.emit("game_resumed")

    @inthread
    def resume(self):
        self.applyingMoveLock.acquire()
        try:
            self.status = RUNNING
            self.__resume()
        finally:
            self.applyingMoveLock.release()

    def end(self, status, reason):
        if self.status not in UNFINISHED_STATES:
            log.info(
                "GameModel.end: Can't end a game that's already ended: %s %s" %
                (status, reason))
            return
        if self.status not in (WAITING_TO_START, PAUSED, RUNNING):
            self.needsSave = True

        log.debug("GameModel.end: players=%s, self.ply=%s: Ending a game with status %d for reason %d" % (
            repr(self.players), str(self.ply), status, reason))
        self.status = status
        self.reason = reason

        self.emit("game_ended", reason)

        self.__pause()

    def kill(self, reason):
        log.debug("GameModel.kill: players=%s, self.ply=%s: Killing a game for reason %d\n%s" % (
                  repr(self.players), str(self.ply), reason, "".join(
                      traceback.format_list(traceback.extract_stack())).strip()))

        self.status = KILLED
        self.reason = reason

        for player in self.players:
            player.end(self.status, reason)

        for spectator in self.spectators.values():
            spectator.end(self.status, reason)

        if self.timed:
            self.timemodel.end()

        self.emit("game_ended", reason)

    def terminate(self):
        log.debug("GameModel.terminate: %s" % self)
        self.terminated = True

        if self.status != KILLED:
            for player in self.players:
                player.end(self.status, self.reason)

            analyzer_types = list(self.spectators.keys())
            for analyzer_type in analyzer_types:
                self.remove_analyzer(analyzer_type)

            if self.timed:
                log.debug("GameModel.terminate: -> timemodel.end()")
                self.timemodel.end()
                log.debug("GameModel.terminate: <- timemodel.end() %s" %
                          repr(self.timemodel))
                if self.zero_reached_cid is not None:
                    self.timemodel.disconnect(self.zero_reached_cid)

        # ICGameModel may did this if game was a FICS game
        if self.connections is not None:
            for player in self.players:
                for cid in self.connections[player]:
                    player.disconnect(cid)
        self.connections = {}

        self.timemodel.gamemodel = None
        self.players = []
        self.emit("game_terminated")

    # Other stuff

    @inthread
    @undolocked
    def undoMoves(self, moves):
        """ Undo and remove moves number of moves from the game history from
            the GameModel, players, and any spectators """
        if self.ply < 1 or moves < 1:
            return
        if self.ply - moves < 0:
            # There is no way in the current threaded/asynchronous design
            # for the GUI to know that the number of moves it requests to takeback
            # will still be valid once the undo is actually processed. So, until
            # we either add some locking or get a synchronous design, we quietly
            # "fix" the takeback request rather than cause AssertionError or IndexError
            moves = 1

        log.debug("GameModel.undoMoves: players=%s, self.ply=%s, moves=%s, board=%s" % (
                  repr(self.players), self.ply, moves, self.boards[-1]))
        log.debug("GameModel.undoMoves: acquiring self.applyingMoveLock")
        self.applyingMoveLock.acquire()
        log.debug("GameModel.undoMoves: self.applyingMoveLock acquired")
        try:
            self.emit("moves_undoing", moves)
            self.needsSave = True

            self.boards = self.variations[0]
            del self.boards[-moves:]
            del self.moves[-moves:]
            self.boards[-1].board.next = None

            for player in self.players:
                player.playerUndoMoves(moves, self)
            for spectator in self.spectators.values():
                spectator.spectatorUndoMoves(moves, self)

            log.debug("GameModel.undoMoves: undoing timemodel")
            if self.timed:
                self.timemodel.undoMoves(moves)

            self.checkStatus()
            self.setOpening()
        finally:
            log.debug("GameModel.undoMoves: releasing self.applyingMoveLock")
            self.applyingMoveLock.release()

        self.emit("moves_undone", moves)

    def isChanged(self):
        if self.ply == 0:
            return False
        if self.needsSave:
            return True
        if not self.uri or not isWriteable(self.uri):
            return True
        return False

    def add_variation(self, board, moves, comment="", score="", emit=True):
        board0 = board
        board = board0.clone()
        board.board.prev = None

        # this prevents annotation panel node searches to find this instead of board0
        board.board.hash = -1

        if comment:
            board.board.children.append(comment)

        variation = [board]

        for move in moves:
            new = board.move(move)
            if len(variation) == 1:
                new.board.prev = board0.board
                variation[0].board.next = new.board
            else:
                new.board.prev = board.board
                board.board.next = new.board
            variation.append(new)
            board = new

        if board0.board.next is None:
            # If we are in the latest played board, and want to add a variation
            # we have to add a not played yet board first
            # which can hold the variation as his child
            from pychess.Utils.lutils.LBoard import LBoard
            null_board = LBoard()
            null_board.prev = board0.board
            board0.board.next = null_board

        board0.board.next.children.append(
            [vboard.board for vboard in variation])
        if score:
            variation[-1].board.children.append(score)

        head = None
        for vari in self.variations:
            if board0 in vari:
                head = vari
                break

        variation[0] = board0
        self.variations.append(head[:board0.ply - self.lowply] + variation)
        self.needsSave = True
        if emit:
            self.emit("variation_added", board0.board.next.children[-1], board0.board.next)
        return self.variations[-1]

    def add_move2variation(self, board, move, variationIdx):
        new = board.move(move)
        new.board.prev = board.board
        board.board.next = new.board

        # Find the variation (low level lboard list) to append
        cur_board = board.board
        vari = None
        while cur_board.prev is not None:
            for child in cur_board.prev.next.children:
                if isinstance(child, list) and cur_board in child:
                    vari = child
                    break
            if vari is None:
                cur_board = cur_board.prev
            else:
                break
        vari.append(new.board)

        self.variations[variationIdx].append(new)
        self.needsSave = True
        self.emit("variation_extended", board.board, new.board)
Example #32
0
    def __init__(self, timemodel=None, variant=NormalBoard):
        GObject.GObject.__init__(self)
        self.daemon = True
        self.variant = variant
        self.boards = [variant(setup=True)]

        self.moves = []
        self.scores = {}
        self.spy_scores = {}
        self.players = []

        self.gameno = None
        self.variations = [self.boards]

        self.terminated = False
        self.status = WAITING_TO_START
        self.reason = UNKNOWN_REASON
        self.curColor = WHITE

        if timemodel is None:
            self.timemodel = TimeModel()
        else:
            self.timemodel = timemodel
        self.timemodel.gamemodel = self

        self.connections = defaultdict(list)  # mainly for IC subclasses
        self.analyzer_cids = {}
        self.examined = False

        now = datetime.datetime.now()
        self.tags = {
            "Event": _("Local Event"),
            "Site": _("Local Site"),
            "Round": 1,
            "Year": now.year,
            "Month": now.month,
            "Day": now.day,
            "Time": "%02d:%02d:00" % (now.hour, now.minute),
            "Result": "*",
        }

        self.endstatus = None
        self.zero_reached_cid = None

        self.timed = self.timemodel.minutes != 0 or self.timemodel.gain != 0
        if self.timed:
            self.zero_reached_cid = self.timemodel.connect(
                'zero_reached', self.zero_reached)
            if self.timemodel.moves == 0:
                self.tags["TimeControl"] = "%d+%d" % (self.timemodel.minutes *
                                                      60, self.timemodel.gain)
            else:
                self.tags["TimeControl"] = "%d/%d" % (
                    self.timemodel.moves, self.timemodel.minutes * 60)
            # Notice: tags["WhiteClock"] and tags["BlackClock"] are never set
            # on the gamemodel, but simply written or read during saving/
            # loading from pgn. If you want to know the time left for a player,
            # check the time model.

        # Keeps track of offers, so that accepts can be spotted
        self.offers = {}

        # True if the game has been changed since last save
        self.needsSave = False

        # The uri the current game was loaded from, or None if not a loaded game
        self.uri = None

        # Link to additiona info
        self.info = None

        self.spectators = {}

        self.undoQueue = Queue()

        self.offline_lecture = False
        self.practice_game = False
        self.lesson_game = False
Example #33
0
    def __init__(self, timemodel=None, variant=NormalBoard):
        GObject.GObject.__init__(self)
        self.daemon = True
        self.variant = variant
        self.boards = [variant(setup=True)]

        self.moves = []
        self.scores = {}
        self.spy_scores = {}
        self.players = []

        self.gameno = None
        self.variations = [self.boards]

        self.terminated = False
        self.status = WAITING_TO_START
        self.reason = UNKNOWN_REASON
        self.curColor = WHITE

        if timemodel is None:
            self.timemodel = TimeModel()
        else:
            self.timemodel = timemodel
        self.timemodel.gamemodel = self

        self.connections = collections.defaultdict(list)  # mainly for IC subclasses
        self.analyzer_cids = {}
        self.examined = False

        now = datetime.datetime.now()
        self.tags = collections.defaultdict(str)
        self.tags["Event"] = _("Local Event")
        self.tags["Site"] = _("Local Site")
        self.tags["Date"] = "%04d.%02d.%02d" % (now.year, now.month, now.day)
        self.tags["Round"] = "1"

        self.endstatus = None
        self.zero_reached_cid = None

        self.timed = self.timemodel.minutes != 0 or self.timemodel.gain != 0
        if self.timed:
            self.zero_reached_cid = self.timemodel.connect('zero_reached', self.zero_reached)
            if self.timemodel.moves == 0:
                self.tags["TimeControl"] = "%d%s%d" % (self.timemodel.minutes * 60, "+" if self.timemodel.gain >= 0 else "-", abs(self.timemodel.gain))
            else:
                self.tags["TimeControl"] = "%d/%d" % (self.timemodel.moves, self.timemodel.minutes * 60)
            # Notice: tags["WhiteClock"] and tags["BlackClock"] are never set
            # on the gamemodel, but simply written or read during saving/
            # loading from pgn. If you want to know the time left for a player,
            # check the time model.

        # Keeps track of offers, so that accepts can be spotted
        self.offers = {}

        # True if the game has been changed since last save
        self.needsSave = False

        # The uri the current game was loaded from, or None if not a loaded game
        self.uri = None

        # Link to additiona info
        self.info = None

        self.spectators = {}

        self.undoQueue = Queue()

        # learn_type set by LearnModel.set_learn_data()
        self.offline_lecture = False
        self.puzzle_game = False
        self.lesson_game = False
        self.end_game = False
        self.solved = False