def extract_game()

in pyhanabi/tools/extract_human_data.py [0:0]


def extract_game(path, game_id):
    tic = time.perf_counter()

    players = None
    moves = []
    deck = []
    init_hands = {}

    htmls = []
    game_log = None
    for fname in listdir(path):
        if fname.endswith(".html"):
            htmls.append(fname)
        elif fname.endswith(".log"):
            game_log = fname

    num_players = len(htmls)

    for fname in htmls:
        suc = False
        for line in open_file(path, fname):
            if isinstance(line, bytes):
                try:
                    line = line.decode(encoding="utf-8")
                except:
                    continue

            if "gameui.completesetup" not in line:
                continue
            suc = True
            # EVIL EVIL EVIL
            begin = line.find("{")
            assert begin != -1, f"Bad begin: {line}"
            end = line.rfind(",")

            data_str = line[begin:end]

            t0 = time.perf_counter()
            data = json.loads("[" + data_str + "]")[0]
            # pprint.pprint(data)
            if players is None:
                players = [str(p) for p in data["playerorder"]]
                player_names = {str(p): data["players"][p]["name"] for p in players}
            colors = data["colors"]
            deck = [
                json_to_card(data["deck"][str(k)])
                for k in sorted([int(i) for i in data["deck"]])
            ]
            for player_idx, player in enumerate(players):
                hand_json = data["hand" + str(player)]
                hand = [
                    json_to_card(hand_json[str(k)])
                    for k in sorted([int(i) for i in hand_json])
                ]
                if hand[0].value != 6:  # not my hand
                    init_hands[player_names[player]] = hand
        assert suc

    all_cards = deck + [c for h in init_hands.values() for c in h]
    max_color = max(c.color for c in all_cards)
    if max_color != 5:
        # multicolor game, don't record it
        return None
    assert max(c.value for c in all_cards) == 5, "Invalid value for card"

    # with open_file(path, game_log) as f:
    j = json.load(open_file(path, game_log))
    abandoned = False
    score = None
    initial_time = None
    for t in j["data"]["data"]:
        if initial_time is None:
            initial_time = int(t["time"])
        for e in t["data"]:
            type_, args = e["type"], e["args"]
            if type_ == "giveValue":
                moves.append(
                    Move(
                        args["player_name"],
                        args["player_id"],
                        "hintValue",
                        int(args["value"]),
                        args["target_name"],
                        int(t["time"]) - initial_time,
                    )
                )
            elif type_ == "giveColor":
                moves.append(
                    Move(
                        args["player_name"],
                        args["player_id"],
                        "hintColor",
                        int(args["color"]),
                        args["target_name"],
                        int(t["time"]) - initial_time,
                    )
                )
            elif type_ == "playCard":
                moves.append(
                    Move(
                        args["player_name"],
                        args["player_id"],
                        "playCard",
                        int(args["card_id"]),
                        -1,
                        int(t["time"]) - initial_time,
                    )
                )
            elif type_ == "discardCard":
                moves.append(
                    Move(
                        args["player_name"],
                        args["player_id"],
                        "discardCard",
                        int(args["card_id"]),
                        -1,
                        int(t["time"]) - initial_time,
                    )
                )
            elif type_ == "missCard":
                # this is a failed play?
                moves.append(
                    Move(
                        args["player_name"],
                        args["player_id"],
                        "playCard",
                        int(args["card_id"]),
                        -1,
                        int(t["time"]) - initial_time,
                    )
                )
            elif type_ == "newScores":
                # print()
                score = list(args["newScores"].values())[0]
            if (
                type_ == "gameStateChange"
                and "name" in args
                and args["name"] == "gameEnd"
            ):
                if args["args"].get("abandon_forced_by_metasite", False):
                    abandoned = True
            if type_ == "simpleNote" and "choosed to abandon" in e["log"]:
                abandoned = True

    if abandoned and score is None:
        score = 0

    if score is None:
        raise QuietError("No score!")

    # sys.exit()# fixup player name -> id
    if len(moves) < num_players:
        return None

    player_order = [m.player_name for m in moves[:num_players]]
    for move in moves:
        player_name = move.player_name
        move.player_name = move.player_id  # In case names are not unique identifiers
        move.player_id = player_order.index(player_name)
        if move.target_player != -1:
            move.target_player = player_order.index(move.target_player)
    init_hands = [init_hands[name] for name in player_order]

    # now simulate the game for some sanity checking and placing the cards
    sim_hands = [[copy.copy(c) for c in hand] for hand in init_hands]
    sim_deck = [copy.copy(c) for c in deck]
    for move_idx, move in enumerate(moves):
        assert (
            move.player_id == move_idx % num_players
        ), f"{move.player_id} != {move_idx} % {num_players}"
        if move.type == "playCard" or move.type == "discardCard":
            hand = sim_hands[move.player_id]
            suc = False
            for card_idx, card in enumerate(hand):
                if card.id == move.value:
                    suc = True
                    move.value = card_idx
                    hand.pop(card_idx)
                    if sim_deck:
                        hand.append(sim_deck.pop())
                    break

            if not suc:
                raise QuietError(f"Invalid move: {(move, sim_hands[move.player_id])}")

    # print("Players")
    # pprint.pprint(player_order)

    # print("Init hands")
    # pprint.pprint(init_hands)

    # print("Deck")
    # pprint.pprint(deck)

    # print("Moves")
    # pprint.pprint(moves)

    game = Game(game_id, num_players, init_hands, deck, moves, score, abandoned)
    return game