Arcomage Companion

ArcomageCompanion

As of 2019-02-09. See the latest version.

// ==UserScript==
// @name        Arcomage Companion
// @namespace   mochet
// @description ArcomageCompanion
// @include     /^http:\/\/.*(heroeswm\.ru|178\.248\.235\.15)+?\/cgame\.php.*$/
// @version     1.0.0
// ==/UserScript==

//----------------------------------------------------------------------------//

(function()
{

/*******************************************************************************
 *
 * This file contains following classes and functions:
 *
 * Cards (CARDS)
 *  - [private] Card(_id, _name_en, _color, _costs)
 *
 * SysUtils (SU)
 *  - [public] sleep(ms)
 *  - [public] send_get(url)
 *  - [public] get_host()
 *  - [public] show_error(error_string)
 *
 * GameUtils (GU)
 *  - [public] check_login()
 *
 * CardsTracker (CT)
 *  - [public] add_card_after_action(turn, card_id)
 *  - [public] update_tracker_cards(cards, current_turn)
 *  - [public] init_tracker(cards)
 *
 * ArcomageUtils (AU)
 *  - [public] get_cooldown()
 *  - [public] is_game_active()
 *  - [public] is_my_turn_now()
 *  - [public] get_my_cards()
 *  - [public] get_current_turn()
 *  - [public] update_game_state(response_text)
 *  - [public] check_is_game_active()
 *  - [public] init_game()
 *  - [private] cards_to_list(cards)
 *  - [private] get_game_infos()
 *  - [private] calc_cooldown()
 *  - [private] get_card_and_position(input_string)
 *  - [private] get_last_played_card()
 *
 * ArcomageDrawUtils (ADU)
 *  - [public] init_draw()
 *  - [private] create_companion_nodes()
 *  - [private] resize_and_move_flash()
 *  - [private] tracker_add_card(card_id, turn_num)
 *  - [private] tracker_remove_card(card_id, turn_num)
 *  - [private] update_tracker_frame()
 *
 ******************************************************************************/

"use strict";

//----------------------------------------------------------------------------//

/*
 * Settings
 */

// How often do we want to send a request to the server
var UPDATE_REQUESTS_INTERVAL_TIMER = 1000; // 1 second

// How much cards do you want to show in the tracker frame?
var TRACKER_SHOW_CARDS = 50;

// Flash
var FLASH_WIDTH  = 975;
var FLASH_HEIGHT = 595;
var FLASH_CSS = "visibility: visible; width: "+FLASH_WIDTH+"px; left: auto; margin-left: 0px; height: "+FLASH_HEIGHT+"px; top: auto; margin-top: 0px; padding-bottom: 0px;";

// Cards
var CARD_COLOR_BACKG_RED   = "#ff8080";
var CARD_COLOR_BACKG_BLUE  = "#99ccff";
var CARD_COLOR_BACKG_GREEN = "#73ad21";

var CARD_COLOR_BORDER_RED = "#ff3333";
var CARD_COLOR_BORDER_BLUE = "#1a75ff";
var CARD_COLOR_BORDER_GREEN = "#47d147";

// Scrollbar
var SCROLLBAR_COLOR = "#75a3a3"; // #ccc

// Header
var HEADER_CSS = `
    html {
        background-color: #ddd9cd;
    }
    
    .card-red {
        height: 10px;
        width: 270px;
        border-radius: 10px;
        border: 2px solid `+CARD_COLOR_BORDER_RED+`;
        background-color: `+CARD_COLOR_BACKG_RED+`;
    }
    
    .card-blue {
        height: 10px;
        width: 270px;
        border-radius: 10px;
        border: 2px solid `+CARD_COLOR_BORDER_BLUE+`;
        background-color: `+CARD_COLOR_BACKG_BLUE+`;
    }
    
    .card-green {
        height: 10px;
        width: 270px;
        border-radius: 10px;
        border: 2px solid `+CARD_COLOR_BORDER_GREEN+`;
        background-color: `+CARD_COLOR_BACKG_GREEN+`;
    }
    
    .flash {
        border: 3px solid #999999;
        border-radius: 10px;
    }
    
    .tracker {
        height: `+FLASH_HEIGHT+`px;
        width: 285px;
        overflow:auto;
        border: 3px solid #999999;
        border-radius: 10px;
        background-color: #d9d9d9;
    }
    
    .tracker-turn {
        background-color: #ffd480;
        text-align: center;
        border: 1px solid #cc8800;
        border-radius: 10px;
        font-size:15px;
        width:30px;
        display: inline-block;
    }
    
    .tracker-text {
        padding: 0 0 0 5px;
    }
    
    /* Scrollbar styles */
    ::-webkit-scrollbar {
        width: 12px;
        height: 12px;
    }
    
    ::-webkit-scrollbar-track {
        background: #f5f5f5;
        border-radius: 10px;
    }
    
    ::-webkit-scrollbar-thumb {
        border-radius: 10px;
        background: `+SCROLLBAR_COLOR+`;  
    }
    
    ::-webkit-scrollbar-thumb:hover {
        background: #999;  
    }
`;

//----------------------------------------------------------------------------//

/*
 * Constants
 */

var CARDS_NUM = 102;
var MAX_TURN = 2501; // fixed my admin

//----------------------------------------------------------------------------//

var CARDS = new Cards();

function Cards()
{
    this.cards = [
        new Card(0, "Defective ore", "red", 0),
        new Card(1, "Lucky coin", "red", 0),
        new Card(2, "Abundant soil", "red", 1),
        new Card(3, "Miners", "red", 3),
        new Card(4, "Big vein", "red", 4),
        new Card(5, "Dwarf miners", "red", 7),
        new Card(6, "Overtime", "red", 2),
        new Card(7, "Steal technology", "red", 5),
        new Card(8, "Ordinary wall", "red", 2),
        new Card(9, "Large wall", "red", 3),
        new Card(10, "Innovation", "red", 2),
        new Card(11, "Foundation", "red", 3),
        new Card(12, "Tremor", "red", 7),
        new Card(13, "Secret cavern", "red", 8),
        new Card(14, "Earthquake", "red", 0),
        new Card(15, "Fortified wall", "red", 5),
        new Card(16, "Collapse", "red", 4),
        new Card(17, "New equipment", "red", 6),
        new Card(18, "Mine collapse", "red", 0),
        new Card(19, "Great wall", "red", 8),
        new Card(20, "Galleries", "red", 9),
        new Card(21, "Magic mount", "red", 9),
        new Card(22, "Singing coal", "red", 11),
        new Card(23, "Bastion", "red", 13),
        new Card(24, "New successes", "red", 15),
        new Card(25, "Greater wall", "red", 16),
        new Card(26, "Rock caster", "red", 18),
        new Card(27, "Dragon's heart", "red", 24),
        new Card(28, "Slave labor", "red", 7),
        new Card(29, "Stone garden", "red", 1),
        new Card(30, "Subsoil waters", "red", 6),
        new Card(31, "Barracks", "red", 10),
        new Card(32, "Fortification", "red", 14),
        new Card(33, "Shift", "red", 17),
        new Card(34, "Quartz", "blue", 1),
        new Card(35, "Smoky quartz", "blue", 2),
        new Card(36, "Amethyst", "blue", 2),
        new Card(37, "Spell weavers", "blue", 3),
        new Card(38, "Ore vein", "blue", 5),
        new Card(39, "Eclipse", "blue", 4),
        new Card(40, "Die mould", "blue", 6),
        new Card(41, "Crack", "blue", 2),
        new Card(42, "Ruby", "blue", 3),
        new Card(43, "Spear", "blue", 4),
        new Card(44, "Power explosion", "blue", 3),
        new Card(45, "Harmony", "blue", 7),
        new Card(46, "Parity", "blue", 7),
        new Card(47, "Emerald", "blue", 6),
        new Card(48, "Wisdom pearl", "blue", 9),
        new Card(49, "Fission", "blue", 8),
        new Card(50, "Mild stone", "blue", 7),
        new Card(51, "Sapphire", "blue", 10),
        new Card(52, "Dissension", "blue", 5),
        new Card(53, "Fire ruby", "blue", 13),
        new Card(54, "Collaboration", "blue", 4),
        new Card(55, "Crystal shield", "blue", 12),
        new Card(56, "Empathy", "blue", 14),
        new Card(57, "Diamond", "blue", 16),
        new Card(58, "Sanctuary", "blue", 15),
        new Card(59, "Shining stone", "blue", 17),
        new Card(60, "Dragon's eye", "blue", 21),
        new Card(61, "Solidification", "blue", 8),
        new Card(62, "Jewellery", "blue", 0),
        new Card(63, "Rainbow", "blue", 0),
        new Card(64, "Initiation", "blue", 5),
        new Card(65, "Lightning", "blue", 11),
        new Card(66, "Meditation", "blue", 18),
        new Card(67, "Cow rabies", "green", 0),
        new Card(68, "Sprite", "green", 1),
        new Card(69, "Goblins", "green", 1),
        new Card(70, "Minotaur", "green", 3),
        new Card(71, "Goblin army", "green", 3),
        new Card(72, "Goblin archers", "green", 4),
        new Card(73, "Ghost fairy", "green", 6),
        new Card(74, "Orc", "green", 3),
        new Card(75, "Gnomes", "green", 5),
        new Card(76, "Tiny snakes", "green", 6),
        new Card(77, "Troll instructor", "green", 7),
        new Card(78, "Tower gremlin", "green", 8),
        new Card(79, "Full moon", "green", 0),
        new Card(80, "Crusher", "green", 5),
        new Card(81, "Ogre", "green", 6),
        new Card(82, "Rabid sheep", "green", 6),
        new Card(83, "Familiar", "green", 5),
        new Card(84, "Beetle", "green", 8),
        new Card(85, "Werewolf", "green", 9),
        new Card(86, "Caustic cloud", "green", 11),
        new Card(87, "Unicorn", "green", 9),
        new Card(88, "Elven archers", "green", 10),
        new Card(89, "Succubi", "green", 14),
        new Card(90, "Stone devourers", "green", 11),
        new Card(91, "Thief", "green", 12),
        new Card(92, "Stone giant", "green", 15),
        new Card(93, "Vampire", "green", 17),
        new Card(94, "Dragon", "green", 25),
        new Card(95, "Spearman", "green", 2),
        new Card(96, "Dwarf", "green", 2),
        new Card(97, "Berserker", "green", 4),
        new Card(98, "Warrior", "green", 13),
        new Card(99, "Pegasus rider", "green", 18),
        new Card(100, "Prism", "blue", 2),
        new Card(101, "Elven scouts", "green", 2)
    ];
    
    /**************************************************************************/
    function Card(_id, _name_en, _color, _costs)
    {
        this.id = _id;
        this.name_en = _name_en;
        //this.name_ru = _name_ru;
        //this.text_en = _text_en;
        //this.text_ru = _text_ru;
        this.color = _color;
        this.costs = _costs;
    }
}

//----------------------------------------------------------------------------//

var SU = new SysUtils();

function SysUtils()
{
    
    /**************************************************************************/ 
    this.sleep = function(ms)
    {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
    
    /**************************************************************************/
    this.send_get = function(url)
    {
        var xhr = new XMLHttpRequest();
        xhr.open("GET", url, false);
        xhr.overrideMimeType("text/plain; charset=windows-1251");
        xhr.send(null);
        if(xhr.status == 200)
        {
            return xhr.responseText;
        }
        return null;
    }
    
    /**************************************************************************/
    this.get_host = function()
    {
        return window.location.protocol + "//" + window.location.host + "/";
    }
    
    /**************************************************************************/
    this.show_error = function(error_string)
    {
        alert(error_string);
        throw new Error(error_string);
    }
    
}

//----------------------------------------------------------------------------//

var GU = new GameUtils();

function GameUtils()
{
    
    /**************************************************************************/
    this.check_login = function()
    {
        var re = /.*?pl_id=\d+?.*?/gmi;
        if(!re.test(document.cookie))
        {
            show_error("Пользователь не авторизован");
        }
    }
    
}

//----------------------------------------------------------------------------//

var CT = new CardsTracker();

function CardsTracker()
{
    
    var old_turn = 1;
    var cards_last_time_seen = {};
    var cards_last_time_seen_all = new Array(CARDS_NUM).fill(0); // [0, ..., 0]
    
    /**************************************************************************/
    this.add_card_after_action = function(turn, card_id)
    {
        cards_last_time_seen[turn] = card_id;
        old_turn = turn;
    }
    
    /**************************************************************************/
    this.update_tracker_cards = function(cards, current_turn)
    {
        for(let i = 0; i < cards.length; i++)
        {
            let card_id = cards[i];
            cards_last_time_seen_all[card_id] = current_turn;
        }
    }
    
    /**************************************************************************/
    this.init_tracker = function(cards)
    {
        // TODO
    }
    
}

//----------------------------------------------------------------------------//

var AU = new ArcomageUtils();

function ArcomageUtils()
{
    
    var game_infos = {
        game_id             : "",
        pl_id               : "",
        turn                : 0,
        time_left           : 0,
        time_passed         : 0,
        time_remain_turn    : 0,
        time_remain_total   : 0,
        host                : "",
        user_mode           : "", // viewer, player
        game_active         : false,
        max_tower           : 0,
        max_res             : 0,
        who_now             : -1,
        whose_last_turn     : 0, // 0=nobody, 1=player1, 2=player2 
        cooldown            : 45 // ceil[(102-(6+6))/2] = 45
    };
    
    var game_state = {
        "me" : {
            "ore"           : 0,
            "mana"          : 0,
            "stacks"        : 0,
            "mines"         : 0,
            "monasteries"   : 0,
            "barracks"      : 0,
            "tower"         : 0,
            "wall"          : 0,
            "cards"         : [],
            "cardsNum"      : 6,
            "arcNum"        : 0     // internal arcomage ID: 1 or 2
        },
        "enemy" : {
            "ore"           : 0,
            "mana"          : 0,
            "stacks"        : 0,
            "mines"         : 0,
            "monasteries"   : 0,
            "barracks"      : 0,
            "tower"         : 0,
            "wall"          : 0,
            "cards"         : [],
            "cardsNum"      : 6,
            "arcNum"        : 0
        }
    };
    
    var map_player = {
        0 : "enemy",
        1 : "me"
    };
    
    var last_card = -1;
    var do_init = true;
    
    /**************************************************************************/
    this.is_game_active = function()
    {
        return game_infos.game_active;
    }
    
    /**************************************************************************/
    this.is_my_turn_now = function()
    {
        return game_infos.who_now === 1;
    }
    
    /**************************************************************************/
    this.get_my_cards = function()
    {
        return game_state["me"]["cards"];
    }
    
    /**************************************************************************/
    this.get_current_turn = function()
    {
        return game_infos.turn;
    }
    
    /**************************************************************************/
    this.get_cooldown = function()
    {
        return game_infos.cooldown;
    }
    
    /**************************************************************************/
    this.get_last_played_card = function()
    {
        return last_card;
    }
    
    /**************************************************************************/
    function get_card_and_position(input_string)
    {
        var splits   = input_string.split("-");
        var card_id  = +(splits[0].substring(1, splits[0].length)); // d102 or t102
        var position = +splits[1];
        return [card_id, position];
    }
    
    /***************************************************************************
     *
     * Compute cooldown value
     *
     **************************************************************************/
    function calc_cooldown()
    {
        var cards_both = game_state["me"]["cardsNum"] + game_state["enemy"]["cardsNum"];
        game_infos.cooldown = Math.ceil((CARDS_NUM - cards_both)/2);
    }
    
    /***************************************************************************
     *
     * Convert cards' string to array with integers
     *
     **************************************************************************/
    function cards_to_list(cards)
    {
        return cards.split("-").map(Number);
    }
    
    /***************************************************************************
     *
     * Collect infos from "cgame.php"
     *
     **************************************************************************/
    function get_game_infos()
    {
        
        var data = "";
        
        try
        {
            // [Case 1: fullscreen] try to find JS variable with flash parameters
            data = String(unsafeWindow.flashvars.params);
        }
        catch(err)
        {
            // [Case 2: AxB] try to get flash parameters from html
            try
            {
                data = document.getElementsByName("FlashVars")[0].getAttribute("value");
            }
            catch(err)
            {
                show_error("Не могу получить данные об игре");
            }
        }
        if(data !== "")
        {
            var regex1 = /^width\|\d+\|gameid\|\d+\|soundon\|\d+\|chksum\|\d+\|lng\|\d+\|plid\|\d+\|$/g; // fullscreen
            var regex2 = /^params=gameid\|\d+\|soundon\|\d+\|chksum\|\d+\|lng\|\d+\|plid\|\d+\|$/g; // AxB
            
            if(regex1.test(data))
            {
                var infos = data.split("|");
                game_infos.game_id = infos[3];
                game_infos.pl_id   = infos[11];
            }
            else if(regex2.test(data))
            {
                var params = data.split("=")[1];
                var infos  = params.split("|");
                game_infos.game_id = infos[1];
                game_infos.pl_id   = infos[9];
            }
            else
            {
                show_error("Неправильный формат данных об игре");
            }
        }
        else
        {
            show_error("Нет данных об игре");
        }
    }
    
    /***************************************************************************
     *
     * Parse response text which contains game state
     *
     **************************************************************************/
    this.update_game_state = function()
    {
        var url = game_infos.host + "cardsgame.php?" +
            "gameid=" + game_infos.game_id +
            "&pl_id=" + game_infos.pl_id;
        if(do_init)
        {
            url += "&action=getnicks";
        }
        
        var response_text = send_get(url);
        
        var splits = response_text.split("|");
        
        if(splits.length === 34)
        {
            // action=getnicks
            game_infos.max_tower = +splits[2];
            game_infos.max_res   = +splits[3];
            splits = splits.slice(5, 34);
        }
        if(splits.length === 29)
        {
            if(+splits[2] !== 0)
            {
                // player
                game_state["me"]["arcNum"]    = +splits[2];
                game_state["enemy"]["arcNum"] = (+splits[2] + 1) % 2;
                game_infos.user_mode = "player";
            }
            else
            {
                // viewer, now it gets dirty...
                game_infos.user_mode = "viewer";
                
                // TODO
            }
            
            game_infos.turn                    = +splits[0];
            game_infos.who_now                 = +splits[1];
            game_infos.time_left               = +splits[3];
            
            game_state["me"]["ore"]            = +splits[5];
            game_state["me"]["mana"]           = +splits[7];
            game_state["me"]["stacks"]         = +splits[9];
            game_state["me"]["mines"]          = +splits[11];
            game_state["me"]["monasteries"]    = +splits[13];
            game_state["me"]["barracks"]       = +splits[15];
            game_state["me"]["wall"]           = +splits[17];
            game_state["me"]["tower"]          = +splits[19];
            game_state["enemy"]["ore"]         = +splits[4];
            game_state["enemy"]["mana"]        = +splits[6];
            game_state["enemy"]["stacks"]      = +splits[8];
            game_state["enemy"]["mines"]       = +splits[10];
            game_state["enemy"]["monasteries"] = +splits[12];
            game_state["enemy"]["barracks"]    = +splits[14];
            game_state["enemy"]["wall"]        = +splits[16];
            game_state["enemy"]["tower"]       = +splits[18];
            game_state["me"]["cards"]          =  cards_to_list(splits[20]);
            // 21 -> 2 ??? 2
            // 22 -> 4 ??? 1
            
            var cp = get_card_and_position(splits[23]); // d10-2
            game_infos.whose_last_turn = +splits[24];
            
            last_card = cp[0];
            
            if(game_infos.whose_last_turn !== game_state["me"]["arcNum"] && cp[1] === 6 && game_state["enemy"]["cardsNum"] !== 7)
            {
                game_state["enemy"]["cardsNum"] = 7;
                calc_cooldown(); // update cooldown value
            }
            // 25 -> d10-   => last_used_stack_handler
            // 26 -> 0 ???
            // 27 -> 32641965 -> chatid
            /*
            6|7|
            5|6|
            5|5|
            72|38|
            52|48|
            76|46|
            0|35|
            29|39|
            14-61-31-71-23-6-75|
            2|4|
            d10-2|1|d10-|0|0|
            */
            
            // update number of my cards
            game_state["me"]["cardsNum"] = game_state["me"]["cards"].length;
            
            do_init = false;
        }
        else
        {
            show_error("Не могу обновить данные - неправильное количество значений");
        }
    }
    
    /**************************************************************************/
    this.check_is_game_active = function()
    {
        if(game_infos.turn >= MAX_TURN)
        {
            game_infos.game_active = false;
        }
        else if(game_infos.time_left < 0)
        {
            game_infos.game_active = false;
        }
        else
        {
            game_infos.game_active = true;
        }
    }
    
    /**************************************************************************/
    this.init_game = function()
    {
        
        // get game ID + player ID
        get_game_infos();
        
        // get host
        game_infos.host = get_host();
        
        // get all informations about the game and update internal game state
        this.update_game_state();
        
        // check if game is still active
        this.check_is_game_active();
    }
    
}

//----------------------------------------------------------------------------//

var ADU = new ArcomageDrawUtils();

function ArcomageDrawUtils()
{
    
    var tracker_queue = [];
    
    /**************************************************************************/
    function create_companion_nodes()
    {
        document.getElementsByTagName("style")[0].outerHTML = ""; // remove old styles
        document.getElementsByTagName("html")[0].setAttribute("style", "overflow: auto; height: 100%;");
        document.getElementsByTagName("body")[0].setAttribute("style", "height: 100%; padding: 0px; margin: 0px;");
        
        var newHtmlStyle = document.createElement('style');
        newHtmlStyle.innerHTML = HEADER_CSS;
        document.head.appendChild(newHtmlStyle);
        
        var div = document.createElement('div');
        div.id = "arcomageCompanion";
        div.innerHTML = `
          <table>
            <tr>
              <td><div id="flashArcomage"></div></td>
              <td><div class="tracker"><table id="cardsTracker"></table></div></td>
            </tr>
          </table>
        `;
        document.body.appendChild(div);
    }
    
    /**************************************************************************/
    function resize_and_move_flash()
    {
        try
        {
            // [Case 1: fullscreen]
            var _arcomage = document.getElementById("arcomage");
            _arcomage.setAttribute("width", FLASH_WIDTH);
            _arcomage.setAttribute("width", FLASH_WIDTH);
            _arcomage.setAttribute("style", FLASH_CSS);
            
            document.getElementById("flashArcomage").appendChild(_arcomage);
        }
        catch(err)
        {
            try
            {
                // [Case 2: AxB]
                var _object = document.getElementsByTagName("object")[0];
                
                _object.setAttribute("width", FLASH_WIDTH); // name=arcomag
                _object.setAttribute("height", FLASH_HEIGHT); // name=arcomag
                _object.removeAttribute("align");
                _object.setAttribute("style", FLASH_CSS);
                
                
                document.getElementById("flashArcomage").appendChild(_object);
                try
                {
                    // remove "a": >>Вернуться в игру<<
                    var _p = document.getElementsByTagName("p")[0];
                    _p.getElementsByTagName("a")[0].outerHTML = "";
                }
                catch(err)
                {}
                // remove "wrapper"
                document.getElementById("wrapper").outerHTML = "";
            }
            catch(err)
            {
                show_error("Не могу изменить размер и переместить флеш элемент");
            }
        }
    }
    
    /**************************************************************************/
    function tracker_add_card(card_id, turn_num)
    {
        var table = document.getElementById("cardsTracker");
        var row = table.insertRow(0);
        row.id = "trackedCardID-" + card_id + "-" + turn_num;
        
        var cell = row.insertCell(0);
        cell.className = "card-"+cards[card_id].color;
        cell.innerHTML = "<span class='tracker-turn'>"+turn_num+"</span><span class='tracker-text'>" + cards[card_id].name_en+"</span>";
    }
    
    /**************************************************************************/
    function tracker_remove_card(card_id, turn_num)
    {
        try
        {
            document.getElementById("trackedCardID-" + card_id + "-" + turn_num).outerHTML = "";
        }
        catch(err)
        {
            show_error("Не могу удалить карту из трекера (интерфейс)");
        }
    }
    
    /**************************************************************************/
    this.update_tracker_frame = function(card_id, turn_num)
    {
        if(tracker_queue.length === TRACKER_SHOW_CARDS)
        {
            var cp = tracker_queue.shift(); // remove first card
            tracker_remove_card(cp[0], cp[1]); // remove from frame
        }
        tracker_queue.push([card_id, turn_num]); // add new one to the queue
        tracker_add_card(card_id, turn_num); // show new card on top
    }
    
    /**************************************************************************/
    function show_card_info()
    {
        // TODO
    }
    
    /**************************************************************************/
    function show_errors()
    {
        // TODO
    }
    
    /**************************************************************************/
    function show_timers()
    {
        // TODO
    }
    
    /**************************************************************************/
    function show_current_turn()
    {
        // TODO
    }
    
    /**************************************************************************/
    function show_positions()
    {
        // TODO
    }
    
    /**************************************************************************/
    function show_game_dynamics()
    {
        // TODO
    }
    
    /**************************************************************************/
    function show_enemys_cards_prediction()
    {
        // TODO
    }
    
    /**************************************************************************/
    function show_cards_scoring()
    {
        // TODO
    }
    
    /**************************************************************************/
    this.init_draw = function()
    {
        // Create nodes
        create_companion_nodes();
        
        // Resize flash element
        resize_and_move_flash();
    }
    
}

//----------------------------------------------------------------------------//
/*
 * Main
 */

var sleep      = SU.sleep;      // function
var show_error = SU.show_error; // function
var send_get   = SU.send_get;   // function
var get_host   = SU.get_host;   // function
var cards      = CARDS.cards;   // variable

async function ArcomageCompanion(){
    
    GU.check_login(); // is user logged in?
    AU.init_game();   // collect initial data
    ADU.init_draw();  // draw interface
    
    var myCards = AU.get_my_cards();
    CT.init_tracker(myCards); // start tracker
    
    var last_turn = 1;
    
    // main loop
    while(AU.is_game_active())
    {
        AU.update_game_state();
        
        var current_turn = AU.get_current_turn();
        
        if(current_turn > last_turn)
        {
            var card_id = AU.get_last_played_card();
            ADU.update_tracker_frame(card_id, last_turn);
            last_turn = current_turn;
        }
        
        await sleep(UPDATE_REQUESTS_INTERVAL_TIMER);
    }
}

ArcomageCompanion();

//----------------------------------------------------------------------------//

})();

//----------------------------------------------------------------------------//