BGA Pythia - 7 Wonders game helper

Visual aid that extends BGA game interface with useful information

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         BGA Pythia - 7 Wonders game helper
// @description  Visual aid that extends BGA game interface with useful information
// @namespace    https://github.com/dpavliuchkov/bga-pythia
// @author       https://github.com/dpavliuchkov
// @version      1.3.3
// @license      MIT
// @include      *boardgamearena.com/*
// @grant        none
// ==/UserScript==
//
// On boardgamearena.com, you can play an exciting board game of 7 wonders.
// However, it is hard to remember which cards each player has. Pythia has
// godlike powers and will share this information with you. It will also
// display total player's score based on the current shields situation.
// And it will mark leader and runner up players and their boards. And
// Pythia will calculate how much points and coins some cards are worth to you.
// Works with Tampermonkey only.
// ==/UserScript==

// System variables - don't edit
const Enable_Logging = false;
const Is_Inside_Game = /\?table=[0-9]*/.test(window.location.href);
const Cards_Image = "https://x.boardgamearena.net/data/themereleases/current/games/sevenwonders/200914-1526/img/cards.jpg";
const Cards_Image_V2 = "https://x.boardgamearena.net/data/themereleases/current/games/sevenwonders/200914-1526/img/cards_v2.jpg";
const BGA_Player_Board_Id_Prefix = "player_board_wrap_";
const BGA_Player_Score_Id_Prefix = "player_score_";
const Card_Worth_Id_Prefix = "pythia_card_worth_container_";
const Discard_Card_Worth_Id_Prefix = "pythia_discard_card_worth_container_";
const Card_Worth_Class = "pythia_card_worth";
const Card_Worth_Coins_Class = "pythia_card_coins_worth";
const Player_Cards_Id_Prefix = "pythia_cards_wrap_";
const Player_Hand_Card_Id_Prefix = "pythia_hand_card_";
const Player_Score_Id_Prefix = "pythia_score_";
const Player_Military_Power_Id_Prefix = "pythia_military_power_";
const Player_War_Score_Id_Prefix = "pythia_player_war_score_";
const Player_Cards_Div_Class = "pythia_cards_container";
const Player_Score_Span_Class = "pythia_score";
const Player_Leader_Class = "pythia_leader";
const Player_Runnerup_Class = "pythia_runnerup";
const War_Points_Per_Age = {
    "1": 1,
    "2": 3,
    "3": 5
};
const Victory_Points_Image = {
    "-1": "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/minus%201%20point.png?raw=true",
    "-2": "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/minus%202%20points.png?raw=true",
    0: "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/0%20points.png?raw=true",
    1: "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/1%20point.png?raw=true",
    2: "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/2%20points.png?raw=true",
    3: "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/3%20points.png?raw=true",
    4: "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/4%20points.png?raw=true",
    5: "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/5%20points.png?raw=true",
    6: "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/6%20points.png?raw=true",
    7: "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/7%20points.png?raw=true",
    8: "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/8%20points.png?raw=true",
    9: "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/9%20points.png?raw=true",
    10: "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/10%20points.png?raw=true",
    11: "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/11%20points.png?raw=true",
    12: "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/12%20points.png?raw=true",
    13: "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/13%20points.png?raw=true",
    14: "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/14%20points.png?raw=true",
};
const Coins_Image = {
    0: "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/0%20coins.png?raw=true",
    1: "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/1%20coin.png?raw=true",
    2: "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/2%20coins.png?raw=true",
    3: "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/3%20coins.png?raw=true",
    4: "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/4%20coins.png?raw=true",
    5: "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/5%20coins.png?raw=true",
    6: "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/6%20coins.png?raw=true",
    7: "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/7%20coins.png?raw=true",
    8: "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/8%20coins.png?raw=true",
    9: "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/9%20coins.png?raw=true",
    10: "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/10%20coins.png?raw=true",
    11: "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/11%20coins.png?raw=true",
    12: "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/12%20coins.png?raw=true",
    13: "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/13%20coins.png?raw=true",
    14: "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/14%20coins.png?raw=true",
};
const Military_Power_Icon = "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/military-power-icon.png?raw=true";
const HD_Boards = "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/boards_hd.jpg?raw=true";
const HD_Cards = "https://github.com/dpavliuchkov/bga-pythia/blob/master/images/cards_hd.jpg?raw=true&version=1";

// Main Pythia object
var pythia = {
    isStarted: false,
    isFinished: false,
    dojo: null,
    game: null,
    edition: null,
    isNewEdition: null,
    mainPlayer: null,
    currentAge: 1,
    playersCount: 0,
    players: [],

    // Init Pythia
    init: function() {
        this.isStarted = true;
        // Check if the site was loaded correctly
        if (!window.parent || !window.parent.dojo || !window.parent.gameui.gamedatas ||
            !window.parent.gameui.gamedatas.playerorder || !window.parent.gameui.gamedatas.playerorder[0] ||
            !window.parent.gameui.gamedatas.card_types || !window.parent.gameui.gamedatas.wonders) {
            return;
        }
        this.dojo = window.parent.dojo;
        this.game = window.parent.gameui.gamedatas;
        this.edition = parseInt(this.game.game_edition);
        this.isNewEdition = this.edition == 1;

        var playerOrder = this.game.playerorder;
        this.playersCount = playerOrder.length;
        this.mainPlayer = playerOrder[0];
        // local storage stores value as strings, so we need to parse "false" and "true" to get boolean
        this.settings = {
            "enableWarScores": localStorage.getItem("pythia-seetings-warscores") === null ?
                true : String(localStorage.getItem("pythia-seetings-warscores")) == "true",
            "enableRichBoards": localStorage.getItem("pythia-seetings-richboards") === null ?
                true : String(localStorage.getItem("pythia-seetings-richboards")) == "true",
            "enableCardPoints": localStorage.getItem("pythia-seetings-cardpoints") === null ?
                true : String(localStorage.getItem("pythia-seetings-cardpoints")) == "true",
            "enableHD": localStorage.getItem("pythia-seetings-hd") === null ?
                true : String(localStorage.getItem("pythia-seetings-hd")) == "true",
            "enableFullwidth": localStorage.getItem("pythia-seetings-fullwidth") === null ?
                false : String(localStorage.getItem("pythia-seetings-fullwidth")) == "true",
            "showMenu": localStorage.getItem("pythia-seetings-showmenu") === null ?
                true : String(localStorage.getItem("pythia-seetings-showmenu")) == "true",
        };

        for (var i = 0; i < this.playersCount; i++) {
            var playerId = playerOrder[i];
            this.players[playerId] = {
                hand: {},
                coins: 3,
                shields: 0,
                defeats: 0,
                bgaScore: 1,
                warScore: 0,
                wonder: 0,
                wonderStages: 0,
                maxWonderStages: 0,
                playedTypes: {
                    "raw": 0,
                    "man": 0,
                    "com": 0,
                    "mil": 0,
                    "civ": 0,
                    "sci": 0,
                    "gui": 0,
                },
                playedCards: {
                    "lighthouse": false,
                    "haven": false,
                    "ludus": false,
                    "chamberOfCommerce": false,
                    "shipownersGuild": false,
                },
                science: {
                    "gears": 0, // 1 in BGA
                    "tablets": 0, // 2 in BGA
                    "compasses": 0, // 3 in BGA
                    "jokers": 0, // ? in BGA
                }
            };

            // Identify who sits to the left and to the right
            if (playerId == this.mainPlayer) {
                this.players[playerId].left = playerOrder[this.playersCount - 1];
            } else {
                this.players[playerId].left = playerOrder[i - 1];
            }
            if (playerId == playerOrder[this.playersCount - 1]) {
                this.players[playerId].right = this.mainPlayer;
            } else {
                this.players[playerId].right = playerOrder[i + 1];
            }

            this.renderPythiaContainers(playerId);
        }

        this.renderPythiaMenu();
        this.setStyles();

        // Configure Pythia according to settings
        this.togglePythiaSettingPlayerCardsDisplay(false);
        this.togglePythiaSettingWarScoresDisplay(this.settings.enableWarScores);
        this.togglePythiaSettingRichBoardsDisplay(this.settings.enableRichBoards);
        this.togglePythiaSettingCardPointsDisplay(this.settings.enableCardPoints);
        this.togglePythiaSettingHDDisplay(this.settings.enableHD);
        this.togglePythiaSettingFullwidthDisplay(this.settings.enableFullwidth);

        // Connect event handlers to follow game progress
        this.dojo.subscribe("newHand", this, "recordHand");
        this.dojo.subscribe("cardsPlayed", this, "recordTurn");
        this.dojo.subscribe("coinDelta", this, "recordCoins");
        this.dojo.subscribe("discard", this, "recordDiscard");
        this.dojo.subscribe("newWonders", this, "recordWonderChoice");
        this.dojo.subscribe("wonderBuild", this, "recordWonderStage");
        this.dojo.subscribe("updateScore", this, "recordScoreUpdate");
        this.dojo.subscribe("warVictory", this, "recordWarResults");
        this.dojo.subscribe("newAge", this, "changeAge");

        if (Enable_Logging) console.log("PYTHIA: My eyes can see everything!");
        return this;
    },

    // Record which wonder each player has chosen
    recordWonderChoice: function(data) {
        if (Enable_Logging) console.log("PYTHIA: wonders chosen - I got", data);

        // Input check
        if (!data || !data.args || !data.args.wonders || !this.game || !this.game.wonders) {
            return;
        }

        const wonders = Object.keys(data.args.wonders);
        for (const playerId of wonders) {
            const wonderId = data.args.wonders[playerId]
            this.players[playerId].wonder = wonderId;
            this.players[playerId].maxWonderStages = Object.keys(this.game.wonders[wonderId].stages).length;
        }
    },

    // Check what came to main player in the new hand
    recordHand: function(data) {
        if (Enable_Logging) console.log("PYTHIA: new hand - I got", data);

        // Input check
        if (!data || !data.args || !data.args.cards) {
            return;
        }

        // Rotate old hands and render cards
        if (!this.isFirstTurn()) {
            this.passCards();
            this.renderPlayerCards();
            this.renderPlayerCardTooltips();
        }
        // Save new hand to main player
        this.players[this.mainPlayer].hand = data.args.cards;

        // Get card worth in victory points and coins and render it
        for (var cardId in data.args.cards) {
            const playedCard = data.args.cards[cardId];
            const cardWorth = this.calculateCardWorth(this.mainPlayer, playedCard.type);
            this.renderCardPoints(cardId, false, cardWorth.points, cardWorth.coins);
        }
    },

    // Process all cards played by all players
    recordTurn: function(data) {
        if (Enable_Logging) console.log("PYTHIA: cards played - I got", data);

        // Input check
        if (!data || !data.args || !data.args.cards) {
            return;
        }

        var warPlayed = false;

        // Cycle all played cards
        for (var cardId in data.args.cards) {
            const playedCard = data.args.cards[cardId];
            const originalCard = this.game.card_types[playedCard.type];
            const playerId = playedCard.location_arg;
            if (!originalCard.category) return; // Input check

            const cardCategory = originalCard.category;

            // Track if played card was military
            if (originalCard.shield) {
                warPlayed = true;
                this.players[playerId].shields += originalCard.shield;
                this.renderMilitaryPower(playerId);
            }

            // Update played scientific symbols
            if (originalCard.science) {
                this.increaseScienceCounter(playerId, originalCard.science);
            }

            // Update played card types array of the player
            this.players[playerId].playedTypes[cardCategory] += 1;

            // Track cards that we need for points worth feature
            this.increaseCardCounter(playerId, playedCard.type);

            // Delete played card
            if (isObjectEmpty(this.players[playerId].hand)) {
                continue;
            }
            delete this.players[playerId].hand[cardId];
        }
        if (warPlayed) {
            this.calculateWarScores();
        }
    },

    // If there was a trade, update how many coins each player has
    recordCoins: function(data) {
        if (Enable_Logging) console.log("PYTHIA: coins changed - I got", data);

        // Input check
        if (!data || !data.args || !data.args.coinddelta) {
            return;
        }

        this.players[data.args.player_id].coins += data.args.coinddelta;
    },

    // If main player discarded - we know what card it was
    recordDiscard: function(data) {
        if (Enable_Logging) console.log("PYTHIA: card discarded - I got", data);

        // Input check
        if (!data || !data.args || !data.args.card_id || !!data.channelorig) {
            return;
        }

        var player = data.channelorig.substring(9);
        delete this.players[player].hand[data.args.card_id];
    },

    // Record when a wonder stage was played
    recordWonderStage: function(data) {
        if (Enable_Logging) console.log("PYTHIA: wonder built - I got", data);

        // Input check
        if (!data || !data.args || !data.args.step || !data.args.player_id) {
            return;
        }

        const playerId = data.args.player_id;
        const stage = data.args.step;
        var wonderId = this.players[playerId].wonder;

        // We might have corrupted data, refresh it from the game HTML
        if (wonderId == 0) {
            const board = this.dojo.byId("player_board_wonder_" + playerId);
            switch (board.style.backgroundPositionY) {
                case "0%":
                    // Giza A
                    wonderId = 1;
                    break;

                case "7.69231%":
                    // Baby A
                    wonderId = 2;
                    break;

                case "15.3846%":
                    // Oly A
                    wonderId = 3;
                    break;

                case "23.0769%":
                    // Rho A
                    wonderId = 4;
                    break;

                case "30.7692%":
                    // Eph A
                    wonderId = 5;
                    break;

                case "38.4615%":
                    // Alex A
                    wonderId = 6;
                    break;

                case "46.1538%":
                    // Hali A
                    wonderId = 7;
                    break;

                case "53.8462%":
                    // Giza B
                    wonderId = 8;
                    break;

                case "61.5385%":
                    // Baby B
                    wonderId = 9;
                    break;

                case "69.2308%":
                    // Oly B
                    wonderId = 10;
                    break;

                case "76.9231%":
                    // Rho B
                    wonderId = 11;
                    break;

                case "84.6154%":
                    // Eph B
                    wonderId = 12;
                    break;

                case "92.3077%":
                    // Alex B
                    wonderId = 13;
                    break;

                case "100%":
                    // Hali B
                    wonderId = 14;
                    break;
            }

            this.players[playerId].wonder = wonderId;
        }

        this.players[playerId].wonderStages += 1; // increase a counter of built wonder stages

        // Game correctness check
        if (!this.game.wonders[wonderId] || !this.game.wonders[wonderId].stages ||
            !this.game.wonders[wonderId].stages[stage]) {
            return;
        }

        // If Rhodos built a stage - it could have shields
        if (this.game.wonders[wonderId].stages[stage].shield) {
            this.players[playerId].shields += this.game.wonders[wonderId].stages[stage].shield;
            this.calculateWarScores();
            this.renderMilitaryPower(playerId);
        }

        // If Babylon built a stage - it could have science
        if (this.game.wonders[wonderId].stages[stage].science) {
            this.increaseScienceCounter(playerId, this.game.wonders[wonderId].stages[stage].science);
        }

        // If Hali built a stage - it could have discard play
        if (this.game.wonders[wonderId].stages[stage].pickDiscarded) {
            this.calculateDiscardWorth(this);
        }
    },

    // Update internal scores as well
    recordScoreUpdate: function(data) {
        if (Enable_Logging) console.log("PYTHIA: scores updated - I got", data);

        // Input check
        if (!data || !data.args || !data.args.scores) {
            return;
        }

        const scores = Object.keys(data.args.scores);
        for (const playerId of scores) {
            this.players[playerId].bgaScore = data.args.scores[playerId];
            this.renderPlayerScore(playerId);
        }

        // Update leader & runnerup positions
        this.renderLeaderRunnerup();
    },

    // Process war results
    recordWarResults: function(data) {
        if (Enable_Logging) console.log("PYTHIA: war battle happened - I got", data);

        // Input check
        if (!data || !data.args || !data.args.points || !data.args.neighbour_id) {
            return;
        }

        // Save defeat tokens
        if (data.args.points > 0) {
            this.players[data.args.neighbour_id].defeats += 1;
        }

        // If this is the last war - do cleanup
        if (this.currentAge == 3) {
            this.finishGame();
        }
    },

    // Calculate additional score from shields
    calculateWarScores: function() {
        var currentPlayerId = this.mainPlayer;
        var i = 0;
        while (i < this.playersCount) {
            var thisPlayer = this.players[currentPlayerId];
            thisPlayer.warScore = 0;

            // Check battles with right neighbour
            var rightPlayer = this.players[thisPlayer.right];
            if (thisPlayer.shields > rightPlayer.shields) {
                this.increaseWarScore(currentPlayerId, this.currentAge);
            } else if (thisPlayer.shields < rightPlayer.shields) {
                this.decreaseWarScore(currentPlayerId, this.currentAge);
            }

            // Check battles with left neighbour
            var leftPlayer = this.players[thisPlayer.left];
            if (thisPlayer.shields > leftPlayer.shields) {
                this.increaseWarScore(currentPlayerId, this.currentAge);
            } else if (thisPlayer.shields < leftPlayer.shields) {
                this.decreaseWarScore(currentPlayerId, this.currentAge);
            }

            currentPlayerId = thisPlayer.right;
            i++;
        }
    },

    calculateDiscardWorth: function(that, counter = 1) {
        const discardWrapper = that.dojo.byId('discarded_wrap');

        // Check that we can see discard div, else wait
        if (discardWrapper.style.display == "block") {
            const discarded = that.dojo.query("#discarded div");
            for (var i in discarded) {
                const card = discarded[i];
                if (!card.style) {
                    continue;
                }

                // Calculate card position - the only way to find which card is actually in discard
                var posX, posY;
                if (that.isNewEdition) {
                    posX = -255 * parseInt(card.style.backgroundPositionX) / 100;
                    posY = -393 * parseInt(card.style.backgroundPositionY) / 100;
                } else {
                    posX = -parseInt(card.style.width) * parseInt(card.style.backgroundPositionX) / 100;
                    posY = -parseInt(card.style.height) * parseInt(card.style.backgroundPositionY) / 100;
                }

                var cardType = null;
                for (var j in that.game.card_types) {
                    const originalCard = that.game.card_types[j];
                    if (originalCard.backx == posX && originalCard.backy == posY) {
                        cardType = j;
                        break;
                    }
                }

                if (cardType) {
                    const cardWorth = that.calculateCardWorth(that.mainPlayer, cardType);
                    that.renderCardPoints(card.id.substr(15), true, cardWorth.points, cardWorth.coins);
                }
            }
        } else {
            if (counter > 10) {
                clearTimeout();
                return;
            }

            setTimeout(that.calculateDiscardWorth, 2000, that, counter + 1);
        }
    },

    // Get how many coins and victory points this card will bring to this player
    calculateCardWorth: function(playerId, cardType) {
        const leftPlayerId = this.players[playerId].left;
        const rightPlayerId = this.players[playerId].right;

        var worth = {
            points: null,
            coins: null,
        };

        switch (parseInt(cardType)) {
            // Raw materials
            case 1:
            case 2:
            case 3:
            case 4:
            case 5:
            case 6:
            case 7:
            case 8:
            case 9:
            case 10:
            case 28:
            case 28:
            case 30:
            case 31:
                if (this.players[playerId].playedCards.haven) {
                    worth.points += 1;
                }
                if (this.players[playerId].playedCards.shipownersGuild) {
                    worth.points += 1;
                }
                break;


            // Manufactured goods
            case 11:
            case 12:
            case 13:
            case 32:
            case 33:
            case 34:
            case 39:
            case 40:
                if (this.players[playerId].playedCards.shipownersGuild) {
                    worth.points += 1;
                }
                if (this.players[playerId].playedCards.chamberOfCommerce) {
                    worth.points += 2;
                }
                break;

            // Commercial cards
            case 18:
            case 19:
            case 20:
            case 21:
                if (this.players[playerId].playedCards.lighthouse) {
                    worth.points += 1;
                }
                break;

            // Military cards
            case 22:
            case 23:
            case 24:
            case 43:
            case 44:
            case 45:
            case 46:
            case 70:
            case 71:
            case 72:
            case 73:
            case 80:
                if (!this.game.card_types[cardType] || !this.game.card_types[cardType].shield) {
                    break;
                }
                worth.points = this.calculateMilitaryCardWorth(playerId, parseInt(this.game.card_types[cardType].shield));
                if (this.players[playerId].playedCards.ludus) {
                    worth.points += 1;
                }
                break;

            // Scientific symbols
            case 25:
            case 26:
            case 27:
            case 47:
            case 48:
            case 49:
            case 50:
            case 58:
            case 74:
            case 75:
            case 76:
            case 77:
            case 78:
                if (!this.game.card_types[cardType] || !this.game.card_types[cardType].science) {
                    break;
                }
                worth.points = this.calculateScienceCardWorth(playerId, this.game.card_types[cardType].science);
                break;

            // Age 2 commerce
            case 41: // Vineyard - coins for brown cards
                worth.coins = this.players[leftPlayerId].playedTypes["raw"] + this.players[rightPlayerId].playedTypes["raw"] +
                    this.players[playerId].playedTypes["raw"];
                if (this.players[playerId].playedCards.lighthouse) {
                    worth.points += 1;
                }
                break;

            case 42: // Bazaar - coins for grey cards
                worth.coins = (this.players[leftPlayerId].playedTypes["man"] + this.players[rightPlayerId].playedTypes["man"] +
                    this.players[playerId].playedTypes["man"]) * 2;
                if (this.players[playerId].playedCards.lighthouse) {
                    worth.points += 1;
                }
                break;

            // Age 3 guilds
            case 51: // Workers guild - brown cards
                worth.points = this.players[leftPlayerId].playedTypes["raw"] + this.players[rightPlayerId].playedTypes["raw"];
                if (this.players[playerId].playedCards.shipownersGuild) {
                    worth.points += 1;
                }
                break;
            case 52: // Craftsmens guild - grey cards
                worth.points = 2 * (this.players[leftPlayerId].playedTypes["man"] + this.players[rightPlayerId].playedTypes["man"]);
                if (this.players[playerId].playedCards.shipownersGuild) {
                    worth.points += 1;
                }
                break;
            case 53: // Traders guild - yellow cards
                worth.points = this.players[leftPlayerId].playedTypes["com"] + this.players[rightPlayerId].playedTypes["com"];
                if (this.players[playerId].playedCards.shipownersGuild) {
                    worth.points += 1;
                }
                break;
            case 54: // Philosopehrs guild - yellow cards
                worth.points = this.players[leftPlayerId].playedTypes["sci"] + this.players[rightPlayerId].playedTypes["sci"];
                if (this.players[playerId].playedCards.shipownersGuild) {
                    worth.points += 1;
                }
                break;
            case 55: // Spies guild - red cards
                worth.points = this.players[leftPlayerId].playedTypes["mil"] + this.players[rightPlayerId].playedTypes["mil"];
                if (this.players[playerId].playedCards.shipownersGuild) {
                    worth.points += 1;
                }
                break;
            case 56:
                if (this.isNewEdition) {
                    // Decorators guild - if all stages are built then 7 points
                    worth.points = this.players[playerId].wonderStages == this.players[playerId].maxWonderStages ? 7 : 0;
                } else {
                    // Strategist guild - defeat tokens
                    worth.points = this.players[leftPlayerId].defeats + this.players[rightPlayerId].defeats;
                }
                if (this.players[playerId].playedCards.shipownersGuild) {
                    worth.points += 1;
                }
                break;
            case 57: // Shipowners guild - own brown grey purple cards
                worth.points = this.players[playerId].playedTypes["raw"] + this.players[playerId].playedTypes["man"] +
                    this.players[playerId].playedTypes["gui"] + 1;
                break;
            case 59: // Magistrates guild - blue cards
                worth.points = this.players[leftPlayerId].playedTypes["civ"] + this.players[rightPlayerId].playedTypes["civ"];
                if (this.players[playerId].playedCards.shipownersGuild) {
                    worth.points += 1;
                }
                break;
            case 60: // Builders guild - wonder stages]
                worth.points = this.players[playerId].wonderStages + this.players[leftPlayerId].wonderStages +
                    this.players[rightPlayerId].wonderStages;
                if (this.players[playerId].playedCards.shipownersGuild) {
                    worth.points += 1;
                }
                break;

            // Age 3 commerce
            case 66: // Haven - coins and points for own brown cards
                worth.coins = this.players[playerId].playedTypes["raw"];
                worth.points = this.players[playerId].playedTypes["raw"];
                if (this.players[playerId].playedCards.lighthouse) {
                    worth.points += 1;
                }
                break;
            case 67: // Lighthouse - coins and points for own yellow cards
                worth.coins = this.players[playerId].playedTypes["com"] + 1;
                worth.points = this.players[playerId].playedTypes["com"] + 1;
                break;
            case 68: // Chamber of commerce - coins and points for own grey cards
                worth.coins = this.players[playerId].playedTypes["man"] * 2;
                worth.points = this.players[playerId].playedTypes["man"] * 2;
                if (this.players[playerId].playedCards.lighthouse) {
                    worth.points += 1;
                }
                break;
            case 69: // Arena - coins and points for own wonder stages
                worth.coins = this.players[playerId].wonderStages * 3;
                worth.points = this.players[playerId].wonderStages;
                if (this.players[playerId].playedCards.lighthouse) {
                    worth.points += 1;
                }
                break
            case 79: // Ludus - coins and points for own military stages
                worth.coins = this.players[playerId].playedTypes["mil"] * 3;
                worth.points = this.players[playerId].playedTypes["mil"];
                if (this.players[playerId].playedCards.lighthouse) {
                    worth.points += 1;
                }
                break;
        }

        return worth;
    },

    // How will war score change if a player gets extra shields
    calculateMilitaryCardWorth: function(playerId, extraShields) {
        // Input check
        if (!playerId || !this.players[playerId]) {
            return 0;
        }

        var thisPlayer = this.players[playerId];
        var newWarScore = 0;

        // Check battles with right neighbour
        var rightPlayer = this.players[thisPlayer.right];
        if ((thisPlayer.shields + extraShields) > rightPlayer.shields) {
            newWarScore += War_Points_Per_Age[this.currentAge];
        } else if ((thisPlayer.shields + extraShields) < rightPlayer.shields) {
            newWarScore -= 1;
        }

        // Check battles with left neighbour
        var leftPlayer = this.players[thisPlayer.left];
        if ((thisPlayer.shields + extraShields) > leftPlayer.shields) {
            newWarScore += War_Points_Per_Age[this.currentAge];
        } else if ((thisPlayer.shields + extraShields) < leftPlayer.shields) {
            newWarScore -= 1;
        }

        return newWarScore - thisPlayer.warScore;
    },

    // How many points will this science card bring to a player?
    calculateScienceCardWorth: function(playerId, newSymbol) {
        // Input check
        if (!playerId || !this.players[playerId] || !this.players[playerId].science) {
            return;
        }

        const playerScience = this.players[playerId].science;
        const sciencePointsNow = this.calculateSciencePoints(playerScience.gears, playerScience.tablets,
            playerScience.compasses, playerScience.jokers);
        var sciencePointsAfter = null;

        switch (newSymbol) {
            case 1:
                sciencePointsAfter = this.calculateSciencePoints(playerScience.gears + 1,
                    playerScience.tablets, playerScience.compasses, playerScience.jokers);
                break;

            case 2:
                sciencePointsAfter = this.calculateSciencePoints(playerScience.gears,
                    playerScience.tablets + 1, playerScience.compasses, playerScience.jokers);
                break;

            case 3:
                sciencePointsAfter = this.calculateSciencePoints(playerScience.gears,
                    playerScience.tablets, playerScience.compasses + 1, playerScience.jokers);
                break;

            case "?":
                sciencePointsAfter = this.calculateSciencePoints(playerScience.gears,
                    playerScience.tablets, playerScience.compasses, playerScience.jokers + 1);
                break;

            default:
                break;
        }

        return sciencePointsAfter - sciencePointsNow;
    },

    // How many points will a science set bring?
    calculateSciencePoints: function(gears, tablets, compasses, jokers) {
        // Joker can be any symbol, calculate each option with recursion if we have them
        if (jokers > 0) {
            const pointsWithJokerGear = this.calculateSciencePoints(gears + 1, tablets, compasses, jokers - 1);
            const pointsWithJokerTablet = this.calculateSciencePoints(gears, tablets + 1, compasses, jokers - 1);
            const pointsWithJokerCompass = this.calculateSciencePoints(gears, tablets, compasses + 1, jokers - 1);

            return Math.max(pointsWithJokerGear, pointsWithJokerTablet, pointsWithJokerCompass);
        } else {
            // No jokers - calculate according to the rules
            var points = gears * gears + tablets * tablets + compasses * compasses; // individual symbols
            points += 7 * Math.min(gears, tablets, compasses); // set of 3

            return points;
        }
    },

    // Cleanup things between ages
    changeAge: function(data) {
        if (Enable_Logging) console.log("PYTHIA: new age - I got", data);

        this.currentAge += 1;

        // Recalculate war scores for the new age
        this.calculateWarScores();

        const keys = Object.keys(this.players);
        for (const playerId of keys) {
            // Clean player hands and update total scores
            this.players[playerId].hand = {};
            this.renderPlayerScore(playerId);
            this.renderLeaderRunnerup();
        }

        // Clean rendered cards from previous age
        this.dojo.query("." + Player_Cards_Div_Class).forEach(this.dojo.empty);
    },

    // Cleanup Pythia when the game is done
    finishGame: function() {
        this.isFinished = true;
        this.togglePythiaSettingRichBoardsDisplay(false);
        this.togglePythiaSettingWarScoresDisplay(false);
        this.togglePythiaSettingPlayerCardsDisplay(false);
    },

    // Add war scores based on the age
    increaseWarScore: function(playerId, age) {
        this.players[playerId].warScore += War_Points_Per_Age[age];
    },
    // Decrase war scores
    decreaseWarScore: function(playerId, age) {
        this.players[playerId].warScore -= 1;
    },

    // Add a counter to played science symbols
    increaseScienceCounter: function(player, symbol) {
        switch (symbol) {
            case 1:
                this.players[player].science.gears += 1;
                break;
            case 2:
                this.players[player].science.tablets += 1;
                break;
            case 3:
                this.players[player].science.compasses += 1;
                break;
            case "?":
                this.players[player].science.jokers += 1;
                break;
            default:
                break;
        }
    },

    // Track if certain card was player by hte player, needed for Card Worth function
    increaseCardCounter: function(playerId, cardType) {
        switch (parseInt(cardType)) {
            case 57: // Shipowners guild - own brown grey purple cards
                this.players[playerId].playedCards.shipownersGuild = true;
                break;
            case 66: // Haven - coins and points for own brown cards
                this.players[playerId].playedCards.haven = true;
                break;
            case 67: // Lighthouse - coins and points for own yellow cards
                this.players[playerId].playedCards.lighthouse = true;
                break;
            case 68: // Chamber of commerce - coins and points for own grey cards
                this.players[playerId].playedCards.chamberOfCommerce = true;
                break;
            case 79: // Ludus - coins and points for own military stages
                this.players[playerId].playedCards.ludus = true;
                break;
        }
    },

    // Move cards unplayed cards between players
    passCards: function() {
        // This should be counter to age direction, because
        // Pythia always passes starting from the last player
        var direction = this.currentAge == 2 ? "right" : "left";
        var currentPlayerId = this.mainPlayer;
        var i = 0;
        while (i < this.playersCount) {
            var neighborId = this.players[currentPlayerId][direction];
            this.players[neighborId].hand = this.players[this.players[neighborId][direction]].hand;
            currentPlayerId = neighborId;
            i++;
        }
    },

    // Render player containers
    renderPythiaContainers: function(playerId) {
        // Insert war score container in scores table
        if (!this.dojo.byId(Player_Score_Id_Prefix + playerId)) {
            this.dojo.place(
                "<span id='" + Player_Score_Id_Prefix + playerId + "'" +
                "class='player_score_value " + Player_Score_Span_Class + "'> (1)</span>",
                BGA_Player_Score_Id_Prefix + playerId,
                "after");
        }

        // Insert military power container on player board
        if (!this.dojo.byId(Player_Military_Power_Id_Prefix + playerId)) {
            const refNode = this.dojo.query("#" + BGA_Player_Board_Id_Prefix + playerId + " .sw_coins");
            if (refNode && refNode[0]) {
                this.dojo.place(
                    "<div id='" + Player_Military_Power_Id_Prefix + playerId + "' class='pythia_player_military_power'>" +
                    "<img src='" + Military_Power_Icon + "'/><span>0</span></div>",
                    refNode[0],
                    "last");
            }
        }

        // Insert war score container on player board
        if (!this.dojo.byId(Player_War_Score_Id_Prefix + playerId)) {
            const refNode = this.dojo.query("#" + BGA_Player_Board_Id_Prefix + playerId + " .sw_coins");
            if (refNode && refNode[0]) {
                this.dojo.place(
                    "<div id='" + Player_War_Score_Id_Prefix + playerId + "' class='pythia_player_war_score'>" +
                    "<i class='fa fa-star'></i><span>1 (1)</span></div>",
                    refNode[0],
                    "first");
            }
        }

        // Skip card container for main player and if already rendered
        if (playerId == this.mainPlayer || this.dojo.byId(Player_Cards_Id_Prefix + playerId)) {
            return;
        }
        // Insert card container on player board
        this.dojo.place("<div id='" + Player_Cards_Id_Prefix + playerId + "'" +
            " class='" + Player_Cards_Div_Class + "'></div>",
            BGA_Player_Board_Id_Prefix + playerId,
            "first");
    },

    // Render player hands
    renderPlayerCards: function() {
        const keys = Object.keys(this.players);
        for (const playerId of keys) {
            if (playerId == this.mainPlayer || isObjectEmpty(this.players[playerId].hand)) {
                continue;
            }

            var cardsHTML = "";
            var left = 1;
            for (var card in this.players[playerId].hand) {
                var playedCard = this.game.card_types[this.players[playerId].hand[card].type];
                var posX = -playedCard.backx;
                var posY = -playedCard.backy;
                const cardHtmlId = Player_Hand_Card_Id_Prefix + card;

                cardsHTML += "<div id='" + cardHtmlId + "' style='left: " + left + "px;'>"
                cardsHTML += "<div style='background-position: " + posX + "px " + posY + "px;'>";
                cardsHTML += "<span>" + playedCard.nametr + "</span></div></div>";

                left += 79;
            }
            this.dojo.place(cardsHTML, Player_Cards_Id_Prefix + playerId, "only");
        }
    },


    // Render tooltips for cards in player hands
    renderPlayerCardTooltips: function() {
        const keys = Object.keys(this.players);
        for (const playerId of keys) {
            if (playerId == this.mainPlayer || isObjectEmpty(this.players[playerId].hand)) {
                continue;
            }
            for (var card in this.players[playerId].hand) {
                const tooltipId = "player_hand_item_" + card;

                // Game correctness check
                if (!window.parent.gameui.addTooltipHtml || !window.parent.gameui.tooltips ||
                    !window.parent.gameui.tooltips[tooltipId] || !window.parent.gameui.tooltips[tooltipId].label) {
                    continue;
                }

                window.parent.gameui.addTooltipHtml(Player_Hand_Card_Id_Prefix + card, window.parent.gameui.tooltips[tooltipId].label);
            }
        }
    },

    // Update total player score
    renderPlayerScore: function(playerId, score = 0) {
        if (this.isFinished) {
            return;
        }

        var playerScore = this.dojo.byId(Player_Score_Id_Prefix + playerId);
        if (playerScore) {
            const totalScore = this.players[playerId].bgaScore + this.players[playerId].warScore;
            playerScore.innerHTML = " (" + totalScore + ")";
            this.dojo.query("#" + Player_War_Score_Id_Prefix + playerId + " span")[0]
                .innerHTML = this.players[playerId].bgaScore + " (" + totalScore + ")";
        }
    },

    // Add border and position of leader and runnerup players
    renderLeaderRunnerup: function() {
        if (!this.settings.enableRichBoards || this.isFinished) {
            return;
        }

        // Clean previous leader & runnerup
        this.dojo.query("." + Player_Leader_Class + ", ." + Player_Runnerup_Class)
            .removeClass([Player_Leader_Class, Player_Runnerup_Class]);

        // Find leader and runner ups
        var totalScores = [];
        const keys = Object.keys(this.players);
        for (const playerId of keys) {
            totalScores.push(
                [playerId,
                    this.players[playerId].bgaScore + this.players[playerId].warScore,
                    this.players[playerId].coins
                ]);
        }

        totalScores.sort(function(a, b) {
            return b[1] - a[1] || b[2] - a[2];;
        });

        // Mark new ones
        this.dojo.addClass(BGA_Player_Board_Id_Prefix + totalScores[0][0], Player_Leader_Class);
        this.dojo.addClass(BGA_Player_Board_Id_Prefix + totalScores[1][0], Player_Runnerup_Class);
    },

    // Add laurel wreath and coins icons on top of the card container
    renderCardPoints: function(cardId, isDiscard = false, pointsWorth = null, coinsWorth = null) {
        // Leave if card has no worth info
        if (pointsWorth === null && coinsWorth === null) {
            return;
        }

        // Clean up previous worth in case we saw this card already
        const containerId = isDiscard ? Discard_Card_Worth_Id_Prefix + cardId : Card_Worth_Id_Prefix + cardId;
        this.dojo.destroy(containerId);

        // Build HTML
        const extraClass = this.settings.enableCardPoints ? "" : "pythia_hidden";
        var html = "<span id='" + containerId + "' class='" + Card_Worth_Class + " " + extraClass + "'>";
        if (coinsWorth !== null) {
            html += "<img class='" + Card_Worth_Coins_Class + "' src='" + Coins_Image[coinsWorth] + "' />";
        }
        if (pointsWorth !== null) {
            html += "<img src='" + Victory_Points_Image[pointsWorth] + "' />";
        }
        html += "</span>";

        if (isDiscard) {
            this.dojo.place(html, "discarded_item_" + cardId, "only");
        } else {
            this.dojo.place(html, "cardmenu_" + cardId, "after");
        }
    },

    // Render shields icon next to player coins
    renderMilitaryPower: function(playerId) {
        if (!playerId || !this.players[playerId] || this.isFinished) {
            return;
        }

        var container = this.dojo.query("#" + Player_Military_Power_Id_Prefix + playerId + " span");
        if (container[0]) {
            container[0].innerHTML = this.players[playerId].shields;
        }
    },

    // Render Pythia menu
    renderPythiaMenu: function() {
        var menuHtml = "<div id='pythia_menu'>";
        menuHtml += "<div class='menu_header'>";
        menuHtml += "<h3>PYTHIA v" + GM_info.script.version + "</h3>";

        // Show or hide Pythia menu based on the setting
        if (this.settings.showMenu) {
            menuHtml += "<div id='pythia_menu_list_toggle' class='toggle_container'><a href='javascript:void(0)' class='collapser' data-target='#pythia_menu .menu_content'>(collapse menu)</a>";
            menuHtml += "<a href='javascript:void(0)' class='expander pythia_hidden' data-target='#pythia_menu .menu_content'>(expand menu)</a>";
            menuHtml += "</div></div>";
            menuHtml += "<div class='menu_content'>";
        } else {
            menuHtml += "<div id='pythia_menu_list_toggle' class='toggle_container'><a href='javascript:void(0)' class='collapser pythia_hidden' data-target='#pythia_menu .menu_content'>(collapse menu)</a>";
            menuHtml += "<a href='javascript:void(0)' class='expander' data-target='#pythia_menu .menu_content'>(expand menu)</a>";
            menuHtml += "</div></div>";
            menuHtml += "<div class='menu_content pythia_hidden'>";
        }

        // Card Points setting
        menuHtml += "<div id='pythia_menu_cardpoints' class='menu_item'><span class='title'>Cards Worth:</span>";
        menuHtml += "<span class='status'>Enabled</span><button type='button'>Disable</button></div>";

        // Rich Boards setting
        menuHtml += "<div id='pythia_menu_richboards' class='menu_item'><span class='title'>Rich Boards:</span>";
        menuHtml += "<span class='status'>Enabled</span><button type='button'>Disable</button></div>";

        // War scores setting
        menuHtml += "<div id='pythia_menu_warscores' class='menu_item'><span class='title'>War Scores:</span>";
        menuHtml += "<span class='status'>Enabled</span><button type='button'>Disable</button></div>";

        // HD images setting
        menuHtml += "<div id='pythia_menu_hd' class='menu_item'><span class='title'>HD Images:</span>";
        menuHtml += "<span class='status'>Enabled</span><button type='button'>Disable</button></div>";

        // Fullwidth setting
        menuHtml += "<div id='pythia_menu_fullwidth' class='menu_item'><span class='title'>Full Width:</span>";
        menuHtml += "<span class='status'>Enabled</span><button type='button'>Disable</button></div>";

        menuHtml += "</div>";
        menuHtml += "</div>";
        this.dojo.place(menuHtml, "logs_wrap", "before");

        // Set correct texts based on settings
        this.togglePythiaSettingText("pythia_menu_warscores", this.settings.enableWarScores);
        this.togglePythiaSettingText("pythia_menu_richboards", this.settings.enableRichBoards);
        this.togglePythiaSettingText("pythia_menu_cardpoints", this.settings.enableCardPoints);
        this.togglePythiaSettingText("pythia_menu_hd", this.settings.enableHD);
        this.togglePythiaSettingText("pythia_menu_fullwidth", this.settings.enableFullwidth);

        // Connect event handlers
        this.dojo.connect(this.dojo.query("button", "pythia_menu_warscores")[0], "onclick", this, "togglePythiaSettingWarScores");
        this.dojo.connect(this.dojo.query("button", "pythia_menu_richboards")[0], "onclick", this, "togglePythiaSettingRichBoards");
        this.dojo.connect(this.dojo.query("button", "pythia_menu_cardpoints")[0], "onclick", this, "togglePythiaSettingCardPoints");
        this.dojo.connect(this.dojo.query("button", "pythia_menu_hd")[0], "onclick", this, "togglePythiaSettingHD");
        this.dojo.connect(this.dojo.query("button", "pythia_menu_fullwidth")[0], "onclick", this, "togglePythiaSettingFullwidth");

        // Expand & collapse menu handlers
        this.dojo.connect(this.dojo.query(".menu_header .collapser")[0], "onclick", this, "togglePythiaSettingMenu");
        this.dojo.connect(this.dojo.query(".menu_header .expander")[0], "onclick", this, "togglePythiaSettingMenu");
        this.dojo.connect(this.dojo.query(".menu_header .collapser")[0], "onclick", this, "toggleCollapserExpander");
        this.dojo.connect(this.dojo.query(".menu_header .expander")[0], "onclick", this, "toggleCollapserExpander");
    },

    // Enable or disable display of cards in player hands
    togglePythiaSettingPlayerCardsDisplay: function(pleaseShow) {
        if (pleaseShow) {
            this.dojo.query("." + Player_Cards_Div_Class).removeClass("pythia_hidden");
            this.dojo.query(".sw_coins", "boardspaces").addClass("pythia_player_cards_enabled");
        } else {
            this.dojo.query("." + Player_Cards_Div_Class).addClass("pythia_hidden");
            this.dojo.query(".sw_coins", "boardspaces").removeClass("pythia_player_cards_enabled");
        }
    },

    // Enable or disable display of war scores
    togglePythiaSettingWarScores: function(event) {
        this.settings.enableWarScores = !this.settings.enableWarScores;
        localStorage.setItem("pythia-seetings-warscores", this.settings.enableWarScores);
        this.togglePythiaSettingWarScoresDisplay(this.settings.enableWarScores);
        this.togglePythiaSettingText(event.target.parentNode.id, this.settings.enableWarScores);
    },
    togglePythiaSettingWarScoresDisplay: function(pleaseShow) {
        if (pleaseShow) {
            this.dojo.query("." + Player_Score_Span_Class).removeClass("pythia_hidden");
        } else {
            this.dojo.query("." + Player_Score_Span_Class).addClass("pythia_hidden");
        }
    },

    // Enable or disable display of leader and runnerup positions
    togglePythiaSettingRichBoards: function(event) {
        this.settings.enableRichBoards = !this.settings.enableRichBoards;
        localStorage.setItem("pythia-seetings-richboards", this.settings.enableRichBoards);
        this.togglePythiaSettingRichBoardsDisplay(this.settings.enableRichBoards);
        this.togglePythiaSettingText(event.target.parentNode.id, this.settings.enableRichBoards);
    },
    togglePythiaSettingRichBoardsDisplay: function(pleaseShow) {
        if (pleaseShow) {
            this.renderLeaderRunnerup();
            this.dojo.query(".pythia_player_war_score").removeClass("pythia_hidden");
            this.dojo.query(".pythia_player_military_power").removeClass("pythia_hidden");
        } else {
            this.dojo.query("." + Player_Leader_Class + ", ." + Player_Runnerup_Class)
                .removeClass([Player_Leader_Class, Player_Runnerup_Class]);

            this.dojo.query(".pythia_player_war_score").addClass("pythia_hidden");
            this.dojo.query(".pythia_player_military_power").addClass("pythia_hidden");
        }
    },

    // Enable or disable display of cards points worth
    togglePythiaSettingCardPoints: function(event) {
        this.settings.enableCardPoints = !this.settings.enableCardPoints;
        localStorage.setItem("pythia-seetings-cardpoints", this.settings.enableCardPoints);
        this.togglePythiaSettingCardPointsDisplay(this.settings.enableCardPoints);
        this.togglePythiaSettingText(event.target.parentNode.id, this.settings.enableCardPoints);
    },
    togglePythiaSettingCardPointsDisplay: function(pleaseShow) {
        if (pleaseShow) {
            this.dojo.query("." + Card_Worth_Class).removeClass("pythia_hidden");
        } else {
            this.dojo.query("." + Card_Worth_Class).addClass("pythia_hidden");
        }
    },

    // Enable or disable HD graphics
    togglePythiaSettingHD: function(event) {
        this.settings.enableHD = !this.settings.enableHD;
        localStorage.setItem("pythia-seetings-hd", this.settings.enableHD);
        this.togglePythiaSettingHDDisplay(this.settings.enableHD);
        this.togglePythiaSettingText(event.target.parentNode.id, this.settings.enableHD);
    },
    togglePythiaSettingHDDisplay: function(pleaseShow) {
        this.dojo.query("body").toggleClass("pythia_hd", pleaseShow);
    },

    // Hide or show elements for fullwidth mode
    togglePythiaSettingFullwidth: function(event) {
        this.settings.enableFullwidth = !this.settings.enableFullwidth;
        localStorage.setItem("pythia-seetings-fullwidth", this.settings.enableFullwidth);
        this.togglePythiaSettingFullwidthDisplay(this.settings.enableFullwidth);
        this.togglePythiaSettingText(event.target.parentNode.id, this.settings.enableFullwidth);
    },
    togglePythiaSettingFullwidthDisplay: function(pleaseShow) {
        this.dojo.query("body").toggleClass("pythia_fullwidth", pleaseShow);
        this.dojo.toggleClass("right-side-first-part", "pythia_hidden", pleaseShow);
        this.dojo.toggleClass("logs_wrap", "pythia_hidden", pleaseShow);
    },

    togglePythiaSettingMenu: function(event) {
        this.settings.showMenu = event.target.className == "expander";
        localStorage.setItem("pythia-seetings-showmenu", this.settings.showMenu);
    },

    // Switch enable/disable text in Pythia settings
    togglePythiaSettingText: function(parentId, isEnabled) {
        if (isEnabled) {
            this.dojo.query(".status", parentId)
                .addClass("enabled")
                .removeClass("disabled")[0]
                .innerHTML = "Enabled";
            this.dojo.query("button", parentId)[0].innerHTML = "Disable";
        } else {
            this.dojo.query(".status", parentId)
                .addClass("disabled")
                .removeClass("enabled")[0]
                .innerHTML = "Disabled";
            this.dojo.query("button", parentId)[0].innerHTML = "Enable";
        }
    },

    // Toggle hide / show of any visual element given data-target
    toggleCollapserExpander: function(event) {
        this.dojo.query(".collapser, .expander", event.target.parentNode).toggleClass("pythia_hidden");
        this.dojo.query(event.target.getAttribute('data-target')).toggleClass("pythia_hidden");
    },

    // Is this the first turn of the age?
    isFirstTurn: function() {
        return isObjectEmpty(this.players[this.mainPlayer].hand);
    },

    // Set Pythia CSS styles
    setStyles: function() {
        this.dojo.query("body").addClass("pythia_enabled");
        this.dojo.place(
            "<style type='text/css' id='Pythia_Styles'>" +
            // Generic settings
            ".pythia_player_cards_enabled.sw_coins { top: 50px; } " +
            ".pythia_enabled.arena_mode .player_elo_wrap { visibility: visible; }" +
            ".pythia_enabled #player_board_wrap_" + this.mainPlayer + " .sw_coins { top: 0px; } " +
            ".pythia_enabled #player_hand_wrap { padding-top: 58px; } " +
            ".pythia_enabled #discarded_wrap h3 { padding-bottom: 60px; } " +
            ".pythia_enabled #howto_tutorial { display: none; } " +
            ".pythia_enabled .pythia_hidden { display: none; } " +
            ".pythia_enabled .toggle_container { display: inline; } " +

            // Pythia menu
            "#pythia_menu { font-size: 14px; padding: 10px; position: relative; background-color: #e9d6bf; border: 1px solid black; border-radius: 5px; } " +
            "#pythia_menu .menu_header h3 { display: inline; } " +
            "#pythia_menu .menu_header a { margin-left: 5px; } " +
            "#pythia_menu .menu_content { margin-top: 10px; } " +
            "#pythia_menu .menu_item { height: 26px; } " +
            "#pythia_menu .menu_item span.title { width: 90px; display: inline-block;} " +
            "#pythia_menu .menu_item span.status { text-align: center; width: 55px; display: inline-block; } " +
            "#pythia_menu .menu_item span.status.enabled { color: green; } " +
            "#pythia_menu .menu_item span.status.disabled { color: red; } " +
            "#pythia_menu .menu_item button { width: 60px; padding: 3px; border-radius: 5px; margin-left: 10px; } " +

            // Player cards
            "." + Player_Cards_Div_Class + " { display: block; height: 50px; } " +
            "." + Player_Cards_Div_Class + " div { position: absolute; top: 11px; } " +
            "." + Player_Cards_Div_Class + " div div { background-image: url(" + Cards_Image + "); width: 128px; height: 45px; zoom: 0.6; } " +
            "." + Player_Cards_Div_Class + " div div span { width: 100%; text-align: center; position: absolute; left: 0; top: -25px; font-size: 18px; color: black; cursor: default; } " +

            // Rich boards
            "." + Player_Leader_Class + ", ." + Player_Runnerup_Class + " { border: 5px solid; } " +
            "." + Player_Leader_Class + " { border-color: green; } " +
            "." + Player_Runnerup_Class + " { border-color: red; border-style: inset; } " +
            "." + Player_Leader_Class + " h3::before, ." + Player_Runnerup_Class + " h3::before { float: left; margin-top: -4px; white-space: pre; }" +
            "." + Player_Leader_Class + " h3::before { content: '(Leader) '; color: green; }" +
            "." + Player_Runnerup_Class + " h3::before { content: '(Runner up) '; color: red; }" +
            ".pythia_player_military_power { display: inline; position: relative; top: 3px; }" +
            ".pythia_player_military_power img { width: 30px; padding: 0 4px; }" +
            ".pythia_player_military_power span { position: relative; top: -7px; }" +
            ".pythia_player_war_score { display: inline; padding-right: 4px; position: relative; top: -1px; }" +
            ".pythia_player_war_score i { font-size: 32px; }" +
            ".pythia_player_war_score span { padding-left: 4px; position: relative; top: -3px; }" +

            // Cards worth
            "." + Card_Worth_Class + " { position: absolute; top: -53px; left: 6px; width: 128px; text-align: center; }" +
            "." + Card_Worth_Class + " img { width: 48px; }" +
            "." + Card_Worth_Class + " img." + Card_Worth_Coins_Class + " { position: relative; top: -3px; }" +

            // War scores
            "." + Player_Score_Span_Class + " { display: inline; }" +

            // Fullwidth view
            ".pythia_fullwidth #left-side { margin-right: 0px; } " +

            // New edition styles
            ".new_edition ." + Player_Cards_Div_Class + " div div { background-image: url(" + Cards_Image_V2 + "); width: 255px; height: 110px; zoom: 0.3; text-align: center;} " +
            ".new_edition ." + Player_Cards_Div_Class + " div div span { font-size: 18px; top: 7px; } " +
            ".new_edition .last_board_item { padding-right: 2px; } " +
            ".new_edition .last_board_item .board_item { border-width: 4px; margin: -2px 0 0 -2px; border-color: greenyellow; border-style: outset; } " +
            ".new_edition .last_step_item { border-width: 4px; margin: -4px 0 0 -4px; border-color: greenyellow; border-style: outset; } " +

            // New edition HD boards
            "#pythia_menu_hd { display: none; } " +
            ".new_edition #pythia_menu_hd { display: block; } " +
            ".new_edition.pythia_hd .wonder_face, .new_edition.pythia_hd .player_board_wonder { background-size: 450px 3122px; background-image: url(" + HD_Boards + "); }" +

            "</style>", "sevenwonder_wrap", "last");
    }
};

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

function isObjectEmpty(object) {
    return typeof(object) == "undefined" ||
        (Object.keys(object).length === 0 && object.constructor === Object);
}

// Everything starts here

// Everything starts here
var onload = async function() {
    if (Is_Inside_Game) {
        await sleep(3000); // Wait for BGA to load dojo and 7W scripts
        if (!window.parent || !window.parent.gameui || !window.parent.gameui.game_name ||
            window.parent.gameui.game_name != "sevenwonders") {
            return;
        }

        // Prevent multiple launches
        if (window.parent.isPythiaStarted) {
            return;
        } else {
            if (Enable_Logging) console.log("PYTHIA: I have come to serve you");
            window.parent.isPythiaStarted = true;
            window.parent.pythia = pythia.init();
        }
    }
};

if (document.readyState === "complete") {
    onload();
} else {
    (addEventListener || attachEvent).call(window, addEventListener ? "load" : "onload", onload);
}