예제 #1
0
파일: card.py 프로젝트: antalos/mtgatracker
 def __repr__(self):
     if self.mtga_id != -1:
         return "<Ability, {}: '{}...' {} iid={}>".format(
             all_mtga_cards.find_one(self.source_id).name,
             self.pretty_name[:20], self.mtga_id, self.game_id)
     else:
         return "<UnknownAbility: iid={}>".format(self.game_id)
예제 #2
0
def build_event_texts_from_iid_or_grpid(iid, game, grpid=None):
    if iid < 3:
        return build_card_event_texts(game.get_player_in_seat(iid), game)
    else:
        card_or_ability = game.find_card_by_iid(iid) or game.find_card_by_iid(grpid)
        if not card_or_ability:
            card_or_ability = all_mtga_cards.find_one(iid)
        return build_card_event_texts(card_or_ability, game)
예제 #3
0
def build_card_event_texts(card, game):
    if isinstance(card, Ability):
        owner_is_hero = game.hero == game.get_player_in_seat(card.owner_seat_id)
        text_type = "{}".format("hero" if owner_is_hero else "opponent")
        ability_source = all_mtga_cards.find_one(card.source_id)
        ability_source_text = build_event_text(ability_source.pretty_name, text_type)
        ability_text = build_event_text("ability", "ability", card.pretty_name)
        card_texts = [ability_source_text, "'s ", ability_text]
    elif isinstance(card, Player):
        text_type = "{}".format("hero" if card == game.hero else "opponent")
        card_texts = [build_event_text(card.player_name, text_type)]
    else:  # it's a GameCard
        owner_is_hero = game.hero == game.get_player_in_seat(card.owner_seat_id)
        text_type = "{}".format("hero" if owner_is_hero else "opponent")
        card_texts = [build_event_text(card.pretty_name, text_type)]
    return card_texts
예제 #4
0
def parse_game_state_message(message, timestamp=None):
    # DOM: ok
    import app.mtga_app as mtga_app
    with mtga_app.mtga_watch_app.game_lock:  # the game state may become inconsistent in between these steps, so lock it
        if "turnInfo" in message.keys():
            if "turnNumber" in message["turnInfo"].keys():
                player = app.mtga_app.mtga_watch_app.game.get_player_in_seat(
                    message["turnInfo"]["activePlayer"])
                if "decisionPlayer" in message["turnInfo"].keys():
                    decisionPlayer = app.mtga_app.mtga_watch_app.game.get_player_in_seat(
                        message["turnInfo"]["decisionPlayer"])
                else:
                    decisionPlayer = app.mtga_app.mtga_watch_app.game.last_decision_player
                if timestamp:
                    now = datetime.datetime.now()
                    if app.mtga_app.mtga_watch_app.game.last_log_timestamp is None:
                        app.mtga_app.mtga_watch_app.game.last_log_timestamp = timestamp
                        app.mtga_app.mtga_watch_app.game.last_measured_timestamp = now
                        app.mtga_app.mtga_watch_app.game.log_start_time = timestamp
                        app.mtga_app.mtga_watch_app.game.last_decision_player = decisionPlayer

                    measured_time_diff = now - app.mtga_app.mtga_watch_app.game.last_measured_timestamp
                    log_time_diff = timestamp - app.mtga_app.mtga_watch_app.game.last_log_timestamp

                    if measured_time_diff > log_time_diff:
                        log_time_diff = measured_time_diff  # some turns are really fast, and the logs see it as 0 seconds. Add what we measured instead,

                    app.mtga_app.mtga_watch_app.game.last_log_timestamp = timestamp
                    app.mtga_app.mtga_watch_app.game.last_measured_timestamp = now
                    ct_obj = {
                        "turnInfo":
                        message["turnInfo"],
                        "diff":
                        log_time_diff,
                        "countsAgainst":
                        app.mtga_app.mtga_watch_app.game.last_decision_player
                    }
                    app.mtga_app.mtga_watch_app.game.chess_timer.append(ct_obj)
                    general_output_queue.put({
                        "decisionPlayerChange":
                        True,
                        "heroIsDeciding":
                        decisionPlayer == app.mtga_app.mtga_watch_app.game.hero
                    })
                    app.mtga_app.mtga_watch_app.game.last_decision_player = decisionPlayer
                app.mtga_app.mtga_watch_app.game.turn_number = message[
                    "turnInfo"]["turnNumber"]
                other_player_seat = 2 if message["turnInfo"][
                    "activePlayer"] == 1 else 1
                other_player = app.mtga_app.mtga_watch_app.game.get_player_in_seat(
                    other_player_seat)
                app.mtga_app.mtga_watch_app.game.current_player = player.player_name
                if not app.mtga_app.mtga_watch_app.game.on_the_play:
                    if message["turnInfo"]["turnNumber"] % 2 == 1:
                        app.mtga_app.mtga_watch_app.game.on_the_play = player.player_name
                    else:
                        app.mtga_app.mtga_watch_app.game.on_the_play = other_player.player_name
                app.mtga_app.mtga_watch_app.game.current_phase = message[
                    "turnInfo"]["phase"]
                turn_tuple = (message["turnInfo"]["turnNumber"], "phase")
                if turn_tuple not in mtga_app.mtga_watch_app.game.recorded_targetspecs:
                    mtga_app.mtga_watch_app.game.recorded_targetspecs.append(
                        turn_tuple)
                    turn = turn_tuple[0]
                    active_player_seat = message["turnInfo"]["activePlayer"]
                    active_player = mtga_app.mtga_watch_app.game.get_player_in_seat(
                        active_player_seat)
                    if turn % 2 == 1:
                        text = "{} / {} Turn {}".format(
                            turn, active_player.player_name, int(
                                (turn + 1) / 2))
                    else:
                        text = "{} / {} Turn {}".format(
                            turn, active_player.player_name, int((turn / 2)))
                    text_obj = build_event_text(text, "turn")
                    queue_obj = {"game_history_event": [text_obj]}
                    mtga_app.mtga_watch_app.game.events.append(
                        queue_obj["game_history_event"])
                    general_output_queue.put(queue_obj)
                if "step" in message["turnInfo"].keys():
                    app.mtga_app.mtga_watch_app.game.current_phase += "-{}".format(
                        message["turnInfo"]["step"])
            app.mtga_app.mtga_logger.debug(message["turnInfo"])
        if 'gameInfo' in message.keys():
            if 'matchState' in message['gameInfo']:
                game_number = message['gameInfo']['gameNumber']
                game_player_id = "-game{}-{}".format(
                    game_number, mtga_app.mtga_watch_app.game.hero.player_id)
                match_id_raw = message['gameInfo']['matchID']
                match_id = message['gameInfo']['matchID'] + game_player_id

                if 'results' in message['gameInfo']:
                    results = message['gameInfo']['results']
                    parse_game_results(True, match_id, results)
                if message['gameInfo']['matchState'] == "MatchState_GameInProgress" and \
                        game_number > max(len(app.mtga_app.mtga_watch_app.match.game_results), 1):
                    shared_battlefield = Zone("battlefield")
                    shared_exile = Zone("exile")
                    shared_limbo = Zone("limbo")
                    shared_stack = Zone("stack")
                    new_hero = Player(
                        mtga_app.mtga_watch_app.game.hero.player_name,
                        mtga_app.mtga_watch_app.game.hero.player_id,
                        mtga_app.mtga_watch_app.game.hero.seat,
                        shared_battlefield, shared_exile, shared_limbo,
                        shared_stack,
                        mtga_app.mtga_watch_app.game.hero._deck_cards)

                    new_oppo = Player(
                        mtga_app.mtga_watch_app.game.opponent.player_name,
                        mtga_app.mtga_watch_app.game.opponent.player_id,
                        mtga_app.mtga_watch_app.game.opponent.seat,
                        shared_battlefield, shared_exile, shared_limbo,
                        shared_stack,
                        mtga_app.mtga_watch_app.game.opponent._deck_cards)
                    new_hero.is_hero = True
                    if mtga_app.mtga_watch_app.intend_to_join_game_with:
                        new_hero.original_deck = mtga_app.mtga_watch_app.intend_to_join_game_with
                        new_match_id = match_id_raw + "-game{}-{}".format(
                            game_number, new_hero.player_id)
                        mtga_app.mtga_watch_app.game = Game(
                            new_match_id, new_hero, new_oppo,
                            shared_battlefield, shared_exile, shared_limbo,
                            shared_stack,
                            app.mtga_app.mtga_watch_app.match.event_id,
                            app.mtga_app.mtga_watch_app.match.opponent_rank)
        if 'annotations' in message.keys():
            for annotation in message['annotations']:
                annotation_type = annotation['type'][0]
                if annotation_type == 'AnnotationType_ObjectIdChanged':
                    try:
                        original_id = None
                        new_id = None
                        details = annotation['details']
                        for detail in details:
                            if detail['key'] == "orig_id":
                                original_id = detail["valueInt32"][0]
                                mtga_app.mtga_watch_app.game.ignored_iids.add(
                                    original_id)
                                # NOTE: at one point Spencer thought it might be correct to ignore these AFTER
                                # parsing the whole gameStateMessage, i.e. put these in a list here, and only add them
                                # to ignored_iid's at the end of this function.
                                #
                                # That was incorrect, and led to cards flip-flopping in the UI.
                                # This is correct as is.
                            elif detail['key'] == "new_id":
                                new_id = detail["valueInt32"][0]
                        card_with_iid = mtga_app.mtga_watch_app.game.find_card_by_iid(
                            original_id)
                        if not card_with_iid:  # no one has ref'd yet, we don't care
                            continue
                        new_card_already_exists = mtga_app.mtga_watch_app.game.find_card_by_iid(
                            new_id)
                        if new_card_already_exists:  # just wipe the old card, the new card is already there
                            assert new_card_already_exists.mtga_id == card_with_iid.mtga_id or -1 in [
                                new_card_already_exists.mtga_id,
                                card_with_iid.mtga_id
                            ], "{} / {}".format(
                                new_card_already_exists.mtga_id,
                                card_with_iid.mtga_id)
                            card_with_iid.mtga_id = -1
                        else:
                            card_with_iid.previous_iids.append(original_id)
                            card_with_iid.game_id = new_id
                    except:
                        app.mtga_app.mtga_logger.error(
                            "{}Exception @ count {}".format(
                                util.ld(True),
                                app.mtga_app.mtga_watch_app.error_count))
                        app.mtga_app.mtga_logger.error(
                            "{}parsers:parse_game_state_message - error parsing annotation:"
                            .format(util.ld(True)))
                        app.mtga_app.mtga_logger.error(
                            pprint.pformat(annotation))
                        app.mtga_app.mtga_watch_app.send_error(
                            "Exception during parse AnnotationType_ObjectIdChanged. Check log for more details"
                        )
                if annotation_type == "AnnotationType_TargetSpec":
                    affector_id = annotation["affectorId"]
                    affected_ids = annotation["affectedIds"]
                    affector_card = mtga_app.mtga_watch_app.game.find_card_by_iid(
                        affector_id)
                    if not affector_card:
                        # try abilitiy
                        details = annotation["details"]
                        grpid = None
                        for detail in details:
                            if detail["key"] == "grpid":
                                grpid = detail["valueInt32"][0]
                        affector_card = all_mtga_cards.find_one(grpid)
                    targets = []
                    target_texts = []
                    for affected_id in affected_ids:
                        affected_texts = build_event_texts_from_iid_or_grpid(
                            affected_id, mtga_app.mtga_watch_app.game)
                        target_texts.extend(affected_texts)
                        game_obj = mtga_app.mtga_watch_app.game.find_card_by_iid(
                            affected_id)
                        target = game_obj if game_obj else affected_id
                        targets.append(target)
                    if (
                            affector_card, targets
                    ) not in mtga_app.mtga_watch_app.game.recorded_targetspecs:
                        mtga_app.mtga_watch_app.game.recorded_targetspecs.append(
                            (affector_card, targets))
                        affector_texts = build_card_event_texts(
                            affector_card, mtga_app.mtga_watch_app.game)

                        event_texts = [*affector_texts, " targets "]
                        if len(target_texts) > 2:
                            for target in target_texts:
                                event_texts.extend([target, ", "])
                            event_texts.append(target[-2])
                            event_texts.append(", and")
                            event_texts.append(target[-1])
                        elif len(target_texts) > 1:
                            event_texts.extend(
                                [target_texts[-2], " and ", target_texts[-1]])
                        else:
                            event_texts.extend(target_texts)

                        queue_obj = {"game_history_event": event_texts}
                        mtga_app.mtga_watch_app.game.events.append(
                            queue_obj["game_history_event"])
                        general_output_queue.put(queue_obj)
                if annotation_type == "AnnotationType_ResolutionComplete":
                    try:
                        affector_id = annotation["affectorId"]
                        card = mtga_app.mtga_watch_app.game.find_card_by_iid(
                            affector_id)
                        if isinstance(card, Ability):
                            # card resolutions are handled in annotations below
                            __unused_affected_ids = annotation["affectedIds"]
                            grpid = None
                            details = annotation["details"]
                            for detail in details:
                                if detail["key"] == "grpid":
                                    grpid = detail["valueInt32"][0]
                            resolved_texts = build_event_texts_from_iid_or_grpid(
                                affector_id, mtga_app.mtga_watch_app.game,
                                grpid)
                            event_texts = [*resolved_texts, " resolves"]
                            queue_obj = {"game_history_event": event_texts}
                            mtga_app.mtga_watch_app.game.events.append(
                                queue_obj["game_history_event"])
                            general_output_queue.put(queue_obj)
                    except:
                        app.mtga_app.mtga_logger.error(
                            "{}Exception @ count {}".format(
                                util.ld(True),
                                app.mtga_app.mtga_watch_app.error_count))
                        app.mtga_app.mtga_logger.error(
                            "{}parsers:parse_game_state_message - error parsing annotation:"
                            .format(util.ld(True)))
                        app.mtga_app.mtga_logger.error(
                            pprint.pformat(annotation))
                        app.mtga_app.mtga_watch_app.send_error(
                            "Exception during parse AnnotationType_ResolutionComplete. Check log for more details"
                        )

        if 'gameObjects' in message.keys():
            game_objects = message['gameObjects']
            for object in game_objects:
                card_id = object['grpId']
                instance_id = object['instanceId']
                if instance_id in mtga_app.mtga_watch_app.game.ignored_iids:
                    continue
                owner = object['controllerSeatId']
                type = object["type"]
                zone = object['zoneId']
                if type not in [
                        "GameObjectType_Card", "GameObjectType_Ability",
                        "GameObjectType_SplitCard"
                ]:
                    mtga_app.mtga_watch_app.game.ignored_iids.add(instance_id)
                else:
                    player, zone = mtga_app.mtga_watch_app.game.get_owner_zone_tup(
                        zone)
                    if zone:
                        if not player:
                            player = mtga_app.mtga_watch_app.game.hero
                            # if zone is shared, don't care what player we use to put this card into it
                        assert isinstance(player, Player)
                        if type in [
                                "GameObjectType_Card",
                                "GameObjectType_SplitCard"
                        ]:
                            player.put_instance_id_in_zone(
                                instance_id, owner, zone)
                            zone.match_game_id_to_card(instance_id, card_id)
                        elif type == "GameObjectType_Ability":
                            source_instance_id = object['parentId']
                            source_grp_id = object['objectSourceGrpId']
                            ability_name = all_mtga_cards.find_one(card_id)
                            ability = Ability(ability_name, source_grp_id,
                                              source_instance_id, card_id,
                                              owner, instance_id)
                            zone.abilities.append(ability)
                if "attackState" in object and object[
                        "attackState"] == "AttackState_Attacking":
                    card = mtga_app.mtga_watch_app.game.find_card_by_iid(
                        instance_id)
                    limit_tuple = (mtga_app.mtga_watch_app.game.turn_number,
                                   "attacks", card)
                    if limit_tuple not in mtga_app.mtga_watch_app.game.recorded_targetspecs:
                        mtga_app.mtga_watch_app.game.recorded_targetspecs.append(
                            limit_tuple)
                        card_texts = build_event_texts_from_iid_or_grpid(
                            instance_id, mtga_app.mtga_watch_app.game)
                        event_texts = [*card_texts, " attacking"]
                        queue_obj = {"game_history_event": event_texts}
                        mtga_app.mtga_watch_app.game.events.append(
                            queue_obj["game_history_event"])
                        general_output_queue.put(queue_obj)
                if "blockState" in object and object[
                        "blockState"] == "BlockState_Blocking":
                    card = mtga_app.mtga_watch_app.game.find_card_by_iid(
                        instance_id)
                    block_info = object["blockInfo"]
                    attacker_list = block_info["attackerIds"]
                    for attacker in attacker_list:
                        attacker_card = mtga_app.mtga_watch_app.game.find_card_by_iid(
                            attacker)
                        limit_tuple = (
                            mtga_app.mtga_watch_app.game.turn_number, "blocks",
                            card, attacker_card)
                        if limit_tuple not in mtga_app.mtga_watch_app.game.recorded_targetspecs:
                            mtga_app.mtga_watch_app.game.recorded_targetspecs.append(
                                limit_tuple)
                            attacker_texts = build_event_texts_from_iid_or_grpid(
                                attacker, mtga_app.mtga_watch_app.game)
                            blocker_texts = build_event_texts_from_iid_or_grpid(
                                instance_id, mtga_app.mtga_watch_app.game)

                            event_texts = [
                                *blocker_texts, " blocks ", *attacker_texts
                            ]
                            queue_obj = {"game_history_event": event_texts}
                            mtga_app.mtga_watch_app.game.events.append(
                                queue_obj["game_history_event"])
                            general_output_queue.put(queue_obj)
        if 'zones' in message.keys():
            cards_to_remove_from_zones = {}
            for zone in message['zones']:
                try:
                    removable = parse_zone(zone)
                    if removable:
                        cards_to_remove_from_zones[zone["zoneId"]] = removable
                except:
                    app.mtga_app.mtga_logger.error(
                        "{}Exception @ count {}".format(
                            util.ld(True),
                            app.mtga_app.mtga_watch_app.error_count))
                    app.mtga_app.mtga_logger.error(
                        "{}error parsing zone:".format(util.ld(True)))
                    app.mtga_app.mtga_logger.error(pprint.pformat(zone))
                    app.mtga_app.mtga_watch_app.send_error(
                        "Exception during parse zone. Check log for more details"
                    )
                    import traceback
                    exc = traceback.format_exc()
                    app.mtga_app.mtga_logger.error(exc)
            for zone_id in cards_to_remove_from_zones.keys():
                remove_these = cards_to_remove_from_zones[zone_id]
                player, zone = mtga_app.mtga_watch_app.game.get_owner_zone_tup(
                    zone_id)
                for card in remove_these:
                    if card in zone.cards:
                        zone.cards.remove(card)
        if message[
                "type"] == "GameStateType_Diff" and "players" in message.keys(
                ):
            players = message["players"]
            for player in players:
                seat = player["systemSeatNumber"]
                life_total = player["lifeTotal"]
                player_obj = mtga_app.mtga_watch_app.game.get_player_in_seat(
                    seat)
                if player_obj.current_life_total != life_total:
                    player_is_hero = mtga_app.mtga_watch_app.game.hero == player_obj
                    player_life_text_type = "{}".format(
                        "hero" if player_is_hero else "opponent")
                    player_life_text = build_event_text(
                        player_obj.player_name, player_life_text_type)
                    event_texts = [
                        player_life_text, "'s life total changed ",
                        "{} -> {}".format(player_obj.current_life_total,
                                          life_total)
                    ]
                    queue_obj = {"game_history_event": event_texts}
                    mtga_app.mtga_watch_app.game.events.append(
                        queue_obj["game_history_event"])
                    general_output_queue.put(queue_obj)
                    player_obj.current_life_total = life_total
        # AFTER we've processed gameObjects, look for actions that should go in the log
        # If this code is in the block above gameObjects, then we will end up with lots of
        # "unknown" cards for opponent cards and actions
        if 'annotations' in message.keys():
            for annotation in message['annotations']:
                annotation_type = annotation['type'][0]
                if annotation_type == "AnnotationType_ZoneTransfer":
                    if "affectorId" not in annotation.keys():
                        affector_id = 0
                    else:
                        affector_id = annotation["affectorId"]
                    affected_ids = annotation["affectedIds"]
                    details = annotation["details"]
                    zone_src, zone_dest, category = None, None, None
                    for detail in details:
                        if detail["key"] == "zone_src":
                            zone_src = detail["valueInt32"][0]
                        if detail["key"] == "zone_dest":
                            zone_dest = detail["valueInt32"][0]
                        if detail["key"] == "category":
                            category = detail["valueString"][0]

                    card = mtga_app.mtga_watch_app.game.find_card_by_iid(
                        affected_ids[0])
                    if affector_id == 0:
                        affector_id = card.owner_seat_id
                    player_texts = build_event_texts_from_iid_or_grpid(
                        affector_id, mtga_app.mtga_watch_app.game)
                    annotation_texts = build_event_texts_from_iid_or_grpid(
                        affected_ids[0], mtga_app.mtga_watch_app.game)

                    if category == "PlayLand":
                        event_texts = [
                            *player_texts, " plays ", *annotation_texts
                        ]
                        queue_obj = {"game_history_event": event_texts}
                        mtga_app.mtga_watch_app.game.events.append(
                            queue_obj["game_history_event"])
                        general_output_queue.put(queue_obj)
                    elif category == "Draw":
                        if affector_id > 2:
                            owner = card.owner_seat_id
                            player_texts.extend([
                                ": ", *build_event_texts_from_iid_or_grpid(
                                    owner, mtga_app.mtga_watch_app.game)
                            ])
                        if card.pretty_name == "unknown":
                            event_texts = [*player_texts, " draws"]
                        else:
                            event_texts = [
                                *player_texts, " draws ", *annotation_texts
                            ]
                        queue_obj = {"game_history_event": event_texts}
                        mtga_app.mtga_watch_app.game.events.append(
                            queue_obj["game_history_event"])
                        general_output_queue.put(queue_obj)
                        # build draw log event
                    elif category == "CastSpell":
                        event_texts = [
                            *player_texts, " casts ", *annotation_texts
                        ]
                        queue_obj = {"game_history_event": event_texts}
                        mtga_app.mtga_watch_app.game.events.append(
                            queue_obj["game_history_event"])
                        general_output_queue.put(queue_obj)
                        # build draw log event
                        # TODO: see if this is redundant
                    elif category == "Countered":
                        event_texts = [
                            *player_texts, " counters ", *annotation_texts
                        ]
                        queue_obj = {"game_history_event": event_texts}
                        mtga_app.mtga_watch_app.game.events.append(
                            queue_obj["game_history_event"])
                        general_output_queue.put(queue_obj)
                    elif category == "Resolve":
                        event_texts = [*annotation_texts, " resolves"]
                        queue_obj = {"game_history_event": event_texts}
                        mtga_app.mtga_watch_app.game.events.append(
                            queue_obj["game_history_event"])
                        general_output_queue.put(queue_obj)
                    elif category == "Exile":
                        event_texts = [
                            *player_texts, " exiles ", *annotation_texts
                        ]
                        queue_obj = {"game_history_event": event_texts}
                        mtga_app.mtga_watch_app.game.events.append(
                            queue_obj["game_history_event"])
                        general_output_queue.put(queue_obj)
                    # TODO: category == "Put" ?
                    elif zone_dest == 37 or zone_dest == 33:  # TODO: get rid of this hardcoded bs
                        event_texts = [
                            *annotation_texts, " sent to graveyard ",
                            "(" + category + ")"
                        ]
                        queue_obj = {"game_history_event": event_texts}
                        mtga_app.mtga_watch_app.game.events.append(
                            queue_obj["game_history_event"])
                        general_output_queue.put(queue_obj)