Reference Source

scripts/ui.js

"use strict"
/**
 * @file UI setup and interaction.
 */

// globals
let debug = false;
let game;
let round;
let spin;
let playerCount = 3;
let categorySelectable = false;

let category_editable = false;
let trivia_editable = false;

// keep track of some elements of the table that have been manipulated by the user
let fill_value = "";
let clicked_col;
let clicked_row;
let clicked_element;

let category_form;
let trivia_form;
let category_dialog;
let trivia_dialog;
let frm_name_category = $('input[name="frm_name_category"]');
let frm_name_trivia_question = $('input[name="frm_name_trivia_question"]');
let frm_name_trivia_answer = $('input[name="frm_name_trivia_answer"]');
let tips = $( ".validateTips" );

$(document).ready(function() {
    // hide panels
    hidePanels();

    // click - hide alert
    $("#btn-alert").click(function() {
        $("#alert").hide(); 
    });

    // click - start editing
    $("#btn-start-edit").click(function() {
        $(".start-panel").hide();
        $(".edit-panel").show();

        toggleCategoryClicks();
        toggleTriviaClicks();

        // set the game board to the first round before starting edits
        initGameBoardFromRound(0);
        $('select>option:eq(0)').prop('selected', true);
    });

    // click - finish edit
    $("#btn-stop-edit").click(function() {
        $(".edit-panel").hide();
        $(".start-panel").show();

        toggleCategoryClicks();
        toggleTriviaClicks();

        // set the game board to the first round after edits
        initGameBoardFromRound(0);
    });

    // click - export board
    $("#btn-export").click(function() {
        let roundID = $("#select-round").val() - 1;
        let round = game.getRound(roundID)
        download('board.json', round.board.export());
    });

    // click - add player
    $("#btn-add-player").click(function() {
        if (playerCount < 3) {
            $("#player" + playerCount).show();
            playerCount++;
        } else {
            Alert.danger('Maximum of three players supported');
            Alert.hide(2000);
        }
    });

    // click - del player
    $("#btn-del-player").click(function() {
        if (playerCount > 1) {
            playerCount--;
            $("#player" + playerCount).hide();
        } else {
            Alert.danger('Minimum of one player supported');
            Alert.hide(2000);
        }
    });

    // click - start game
    $("#btn-play").click(function () {
        // set object visibility
        $(".start-panel").hide();
        $(".play-panel").show();
        $(".spin-phase").show();

        // add players to game api
        for (let i = 0; i < playerCount; i++) {
            let playerName = $("#player" + i).val();
            $("#player" + i + " input").prop("readonly", true);

            if (playerName == ""){
                playerName = "Player " + (i + 1);
            }
            game.addPlayer(playerName);
        }

        // start api round
        let id = 0;
        round = game.getRound(id);
        round.start();

        // update wheel
        wheel.buildCreate(round);

        // set player 0 as first
        player.startTurn(0);
    });

    // click - spin
    $("#btn-spin").click(function () {

        if(round.complete){
            if(game.complete){
                display.endGame();
                return;

            } else {
                dialog.endRound();

                // start new round
                game.currentRound++;
                let roundID = game.currentRound;

                round = game.getRound(roundID);
                round.start();

                wheel.buildCreate(round);
                initGameBoardFromRound(roundID);

                clueText.empty();
                playPanel.reset();
                playPanel.setRound(roundID + 1);
                return
            }
        }

        spin = round.spin();

        // update play panel spin count
        playPanel.deductSpin();

        // spin the wheel
        wheel.rotate(spin.slot, function(){
            // selected sector is a category but complete
            if (spin.spinAgain) {
                clueText.write("Category complete, spin again");                
                round.endTurn();

            // selected sector is a category
            } else if (spin.isCategory) {
                displayClue();

            // selected category is a special category
            } else {
                // update the players UI stats
                let currentPlayer = round.currentPlayer;
                player.setPoints(currentPlayer.id, currentPlayer.totalScore);
                player.setTokens(currentPlayer.id, currentPlayer.tokens);

                // special sector
                switch (spin.sectorName) {
                    case "Free Turn":
                        clueText.write("You landed on Free Turn!");                        
                        switchPlayer();
                        break;
                        
                    case "Lose Turn":
                        clueText.write("You landed on Lose Turn!");
                        if (currentPlayer.hasToken()){
                            // present option to use token
                            $(".answer-phase").hide();
                            $(".token-phase").show();
                        } else {
                            // switch to next player
                            switchPlayer();
                        }
                        break;

                    case "Bankrupt":
                        clueText.write("You landed on Bankrupt!");
                        switchPlayer();
                        break;

                    case "Player's Choice":
                        clueText.write("You landed on Player's Choice!\nSelect an open category on the board");
                        categorySelectable = true;
                        $(".spin-phase").hide();
                        break;

                    case "Opponent's Choice":
                        clueText.write("You landed on Opponent's Choice!\nSelect an open category on the board");
                        categorySelectable = true;
                        $(".spin-phase").hide();
                        break;

                    case "Double Score":
                        clueText.write("You landed on Double Score!");
                        switchPlayer();
                        break;
                    default:
                    // do nothing
                }
            }
        });
    });

    // click - answer
    $("#btn-answer").click(function () {
        wheel.stopTimer();
        clueText.write(round.currentClue.answer);

        $(".answer-phase").hide();
        $(".validate-phase").show();
    });

    // click - valid
    $("#btn-valid").click(function () {
        wheel.resetTimer();

        // update board UI
        let clue = round.currentClue;
        board.setValidClue(clue.column, clue.row);

        // update players score in api
        round.validAnswer();

        // update players score in UI
        let currentPlayer = round.currentPlayer;
        player.setPoints(currentPlayer.id, currentPlayer.totalScore);

        $(".validate-phase").hide();
        $(".spin-phase").show();
    });

    // click - invalid
    $("#btn-invalid").click(function () {
        wheel.resetTimer();

        let clue = round.currentClue;
        board.setInvalidClue(clue.column, clue.row);

        // update players score in api
        round.invalidAnswer();

        // update players score in UI
        let currentPlayer = round.currentPlayer;
        player.setPoints(currentPlayer.id, currentPlayer.totalScore);

        if (currentPlayer.hasToken()) {
            // present option to use token
            $(".validate-phase").hide();
            $(".token-phase").show();
        } else {
            // switch to next player
            switchPlayer();
            $(".validate-phase").hide();
            $(".spin-phase").show();
        }
    });

    // click - token
    $("#btn-token").click(function () {
        round.useToken();

        let currentPlayer = round.currentPlayer;
        player.setTokens(currentPlayer.id, currentPlayer.tokens);

        $(".token-phase").hide();
        $(".spin-phase").show();
    });

    // click - end turn
    $("#btn-end-turn").click(function () {
        switchPlayer();
        $(".token-phase").hide();
        $(".spin-phase").show();
    });

    // click - reset
    $("#btn-reset").click(function () {
        game.getRound(0).reset();
        game.getRound(1).reset();

        round = game.getRound(0);
        categorySelectable = false;
        
        wheel.buildCreate(round);
        initGameBoardFromRound(roundID);

        clueText.empty();
        playPanel.reset();
        player.reset();

        wheel.rotate();
        wheel.resetTimer();

        hidePanels();
        $(".start-panel").show();
    }); 

    // set up the edit category dialog
    category_dialog = $("#category-form").dialog({
        autoOpen: false,
        height: 400,
        width: 350,
        modal: true,
        buttons: {
            "Edit Category": editCategory,
            Cancel: function () {
                category_dialog.dialog("close");
            }
        },
        close: function () { frm_name_category.removeClass("ui-state-error"); }
    });

    // setup the edit trivia dialog
    trivia_dialog = $("#trivia-form").dialog({
        autoOpen: false,
        height: 400,
        width: 350,
        modal: true,
        buttons: {
            "Edit Trivia": editTrivia,
            Cancel: function () {
                trivia_dialog.dialog("close");
            }
        },
        close: function () {
            frm_name_trivia_question.removeClass("ui-state-error");
            frm_name_trivia_answer.removeClass("ui-state-error");
        }
    });

    // set up the form to edit the category text
    category_form = category_dialog.find("form").on("submit", function (submit_event) {
        submit_event.preventDefault();
        frm_name_category.val("");
    });

    // set up the form to edit the trivia text
    trivia_form = trivia_dialog.find("form").on("submit", function (submit_event) {
        submit_event.preventDefault();
    });

    // setup the selection handler for the select ddl
    $("#select-round").change(function( clicked_object ) {
        let round_selected = $(this).children(":selected").html();
        console.log("Round changed! " + round_selected);
        let selected_round = parseInt( round_selected) - 1;
        // redraw the category information
        initGameBoardFromRound(selected_round);
    });

    // set up the click event for the trivia elements
    $("#triviaTable td").click(function( clicked_object ) {

        clicked_col = parseInt( $(this).index());
        clicked_row = parseInt( $(this).parent().index());

        clicked_element = clicked_object;
        if ( trivia_editable ) {
            // ensure the form is populated with the current value of the table element before open
            // trivia will extract the current value from the stored (local storage) values depending on the td value read
            let roundID = $("#select-round").val() - 1;
            let clue;
            let output = "Trivia: clicked_row = " + clicked_row + ",  clicked_col = " + clicked_col;
            console.log(output);
            // get the game round if necessary
            round = game.getRound(roundID);
            clue = round.board.getClue(clicked_col, clicked_row);

            // get the q & a for the current round           
            let question = clue.question;
            let answer = clue.answer;
            
            if (typeof question == 'undefined'){ question = "<empty>"; }           
            if (typeof answer == 'undefined'){ answer = "<empty>"; }

            //output = "Clue: Question: " + " | " + question + "\nAnswer: " + answer;
            //console.log(output);
            
            // pre-populate the trivia information before modal open
            frm_name_trivia_question.val(question);
            frm_name_trivia_answer.val(answer);

            trivia_dialog.dialog( "open" );
        }

        // player or opponent choice
        if (categorySelectable) {
            Alert.warning("Select a Category Above.")
        }
    });

    // set up the click event for the category elements
    $("#triviaTable th").click(function( clicked_object ) {
        clicked_element = clicked_object;

        clicked_col = parseInt( $(this).index());
        clicked_row = parseInt( $(this).parent().index());        

        if ( category_editable ) {

            let roundID = $("#select-round").val() - 1;
            let output = "Category: clicked_row = " + clicked_row + ",  clicked_col = " + clicked_col;
            console.log(output);
            // get the game round so categories are edited correctly
            round = game.getRound(roundID);

            // ensure the form is populated with the current value of the table element before open
            frm_name_category.val($(this).text());
            category_dialog.dialog( "open" );
        }

        // player or opponent choice
        if (categorySelectable){
            let category = round.board.getCategory(clicked_col);

            // category is already finished
            if(category.complete){
                let msg = 'Select a different category, ' + category.name + ' is complete'
                Alert.warning(msg);
                Alert.hide(2000);
            } else {
                round.pickCategory(clicked_col);
                categorySelectable = false;
                displayClue();
            }
        }
    });

    // setup the default UI and API
    let roundID = 0;

    // init api objects
    game = engine.initDefaultGame();
    round = game.getRound(roundID);

    // init ui wheel
    wheel.init();
    wheel.buildCreate(round);

    // init ui board
    initGameBoard();

});

// function used to update the trivia modal dialog
function populateTriviaFromEngine(clicked_col, clicked_row) {

    let roundID = 0;
    let round;

    // the update given -1 will read from the UI widget -- e.g. editing mode
    if (gameRound == -1) {
        // get the round chosen from the round drop down box
        roundID = $("#select-round").val() - 1;
        round = game.getRound(roundID);
    }

    round = game.getRound(gameRound);
    // fetch the clue from the engine
    cell_content = round.board.getClue(clicked_col, clicked_row);

}

function initGameBoardFromRound(roundChoice) {

    console.log("Board redraw - round: " + roundChoice);
    let edit_round = game.getRound(roundChoice);

    // setup the points (cells) for the game as chosen in the drop down box
    $("#triviaTable tbody tr").each(function (row_index, row) {
        let current_row = $(row);
        let cols = 6;
        let column_index = 0;
        let current_clue;
        // iterate through the table, adjusting the points to those associated
        // with the chosen board's point value
        for (column_index = 0; column_index < cols; column_index++) {
            let current_column = current_row[0].cells[column_index];
            //console.log("Column Index: " + column_index + ", column text: " + current_column.innerHTML);
            current_clue = edit_round.board.getClue(column_index, row_index);
            current_column.innerHTML = current_clue.points;
        }
    });

    // setup the categories
    let current_categories = edit_round.board.categoryNames;
    $("#triviaTable thead th").each(function (category_index, category) {
        let current_category_cell = $(category);
        //console.log("category Index: " + category_index);
        //console.log("category name: " + current_categories[category_index]);
        //console.log($(category));
        $(category)[0].innerHTML = current_categories[category_index];
    });

    // reset board css
    for(let column=0; column<round.board.columns; column++){
        for (let row = 0; row<round.board.rows; row++) {
            board.removeStyle(column, row);
        }
    }
}

function initGameBoard() {
    // setup the game board assuming round 0
    initGameBoardFromRound(0);
}

function toggleClicks() {
    toggleCategoryClicks();
    toggleTriviaClicks();
}

function toggleCategoryClicks() {
    if (category_editable) { category_editable = false; }
    else { category_editable = true; }
}

function toggleTriviaClicks() {
    if (trivia_editable) { trivia_editable = false; }
    else { trivia_editable = true; }
}

function editCategory() {
    let valid = true;
    frm_name_category.removeClass("ui-state-error");
    valid = valid && checkLength(frm_name_category, "category", 3, 32);
    valid = valid && checkRegexp(frm_name_category, /^[0-9a-zA-Z]([0-9a-zA-Z_\s,\&\^\%\$\#\@\!\)\(\*\[\]\{\}\-\_\.\?\'\‘\’])+$/i, "Input must begin with letters, or numbers; it may contain special characters.");

    // validates the form's input field, and closes the modal
    if (valid) {
        let category_updated = frm_name_category.val();
        clicked_element.target.innerHTML = category_updated;
        console.log("Input: Category Update: " + category_updated);
        round.board.editCategory(clicked_col, category_updated);
        category_dialog.dialog("close");
    }
    return valid;
}

function editTrivia() {
    let valid = true;
    frm_name_trivia_question.removeClass("ui-state-error");
    frm_name_trivia_answer.removeClass("ui-state-error");

    valid = valid && checkLength(frm_name_trivia_question, "trivia", 3, 256);
    valid = valid && checkRegexp(frm_name_trivia_question, /^[0-9a-zA-Z]([0-9a-zA-Z_\s,\&\^\%\$\#\@\!\)\(\*\[\]\{\}\-\_\.\?\'\‘\’])+$/i, "Input must begin with letters, or numbers; it may contain special characters.");
    valid = valid && checkLength(frm_name_trivia_answer, "trivia", 3, 256);
    valid = valid && checkRegexp(frm_name_trivia_answer, /^[0-9a-zA-Z]([0-9a-zA-Z_\s,\&\^\%\$\#\@\!\)\(\*\[\]\{\}\-\_\.\?\'\‘\’])+$/i, "Input must begin with letters, or numbers; it may contain special characters.");

    // validates the form's input field, and closes the modal
    if (valid) {
        // store the user's trivia information in the local session table
        let question = frm_name_trivia_question.val();
        let answer = frm_name_trivia_answer.val();
        console.log("Input: \nQuestion: " + question + "\nAnswer: " + answer);
        round.board.editClue(clicked_col, clicked_row, question, answer);

        trivia_dialog.dialog("close");
    }
    return valid;
}

function updateTips(t) {
    tips
        .text(t)
        .addClass("ui-state-highlight");
    setTimeout(function () {
        tips.removeClass("ui-state-highlight", 3000);
    }, 1500);
}

function checkLength(o, n, min, max) {
    if (o.val().length > max || o.val().length < min) {
        o.addClass("ui-state-error");
        updateTips("Length of " + n + " must be between " + min + " and " + max + ".");
        return false;
    } else {
        return true;
    }
}

function checkRegexp(o, regexp, n) {
    if (!(regexp.test(o.val()))) {
        o.addClass("ui-state-error");
        updateTips(n);
        return false;
    } else { return true; }
}

let player = {
    setPoints: function (id, points){
        $("#player" + id + " .points").text(points);
    },

    setTokens: function (id, tokens){
        $("#player" + id + " .tokens").text(tokens);
    },

    startTurn: function(id){
        $("#player" + id + " input").toggleClass("current-turn", true);
    },
    
    endTurn: function (id) {
        $("#player" + id + " input").toggleClass("current-turn", false);
    },

    readOnlyOn: function (id){
        $("#player" + id + " input").prop("readonly", true);
    },

    readOnlyOff: function (id) {
        $("#player" + id + " input").prop("readonly", false);
    },

    reset: function(){
        for(let i=0; i<playerCount; i++){
            player.setPoints(i, 0);
            player.setTokens(i, 0);
            player.startTurn(i);
            player.endTurn(i);
            player.readOnlyOff(i);
        }
        player.startTurn(0);
        playerCount = 3;
    }
}

let playPanel = {
    getRound: function () {
        return $("#txt-round").text();
    },
    setRound: function(round){
        $("#txt-round").text(round);
    },
    getSpins: function () {
        return $("#txt-spins").text();
    },
    setSpins: function (spins) {
        $("#txt-spins").text(spins);
    },
    getClues: function () {
        return $("#txt-clues").text();
    },
    setClues: function (clues) {
        $("#txt-clues").text(clues);
    },
    deductSpin: function(i=1) {
        playPanel.setSpins(playPanel.getSpins() - i);
    },
    deductClue: function (i=1) {
        playPanel.setClues(playPanel.getClues() - i);
    },
    reset: function(){
        playPanel.setRound(1);
        playPanel.setSpins(50);
        playPanel.setClues(30);
    }
}

let clueText = {
    empty: function(){
        $("#clue-text").text("");
    },
    write: function(txt){
        $("#clue-text").text(txt);
    }
}

let wheel = {
    init: function (svgID = "#wheel") {
        let svg = d3.select(svgID);
        let width = +svg.attr("width");
        let height = +svg.attr("height");
        wheel.radius = Math.min(width, height) / 2;
        wheel.base = svg.append("g")
            .attr("transform", "translate(" + width/2 + "," + height/2 + ")");
        wheel.g = wheel.base
            .append("g");
        wheel.oldAngle = 0;
        wheel.newAngle = 0;

        wheel.width = width;
        wheel.height = height;
    },

    specialSectorLabels: {
        "Bankrupt": '\uf1e2',
        "Opponent's Choice": '\uf0c0',
        "Lose Turn": '\uf119',
        "Double Score": '\uf155\uf155',
        "Player's Choice": '\uf007',
        "Free Turn": '\uf51e'
    },

    buildCreate: function(round){
        let data = wheel.buildData(round);
        wheel.create(data);
    },

    buildData: function(round) {
        let w = round.wheel;
        let data = [];
        
        for(let i=0; i<w.slots.length; i++){
            let slot = {};
            let sectorNumber = w.slots[i];
            let sectorName = w.getSectorName(sectorNumber);
            slot['slot'] = i;
            slot['sector'] = sectorNumber;
            slot['name'] = sectorName;

            if(w.sectorIsCategory(sectorNumber)){
                slot['label'] = 1 + sectorNumber;
            } else {
                slot['label'] = wheel.specialSectorLabels[sectorName];
            }
            data.push(slot);
        }
        return data;
    },

    create: function (data) {
        wheel.data = data;

        wheel.g.selectAll("*").remove();

        let color = d3.scaleOrdinal(d3.schemeSet3);

        let pie = d3.pie()
            .sort(null)
            .value(function (d) { return 1; });

        let path = d3.arc()
            .outerRadius(wheel.radius - 10)
            .innerRadius(40);

        wheel.label = d3.arc()
            .outerRadius(wheel.radius - 40)
            .innerRadius(wheel.radius - 40);

        let arc = wheel.g.selectAll(".arc")
            .data(pie(wheel.data))
            .enter().append("g")
            .attr("class", "arc");

        arc.append("path")
            .attr("d", path)
            .attr("fill", function (d) { return color(d.data.slot); });

        wheel.text = arc.append("text")
            .attr("transform", function (d) {
                return "translate(" + wheel.label.centroid(d) + ")";
            })
            .attr("dy", "0.35em")
            .text(function (d) { return d.data.label; });

        wheel.timerCircle = wheel.base.append("circle")
            .attr("r", "40")
            .attr("fill", "#adc9e2");

        wheel.timerText = wheel.base.append("text")
            .attr("text-anchor", "middle")
            .attr("alignment-baseline", "central")
            .attr("font-size", 32)
            .text("\uf017")
            .attr("id", "timer")
            .classed("fa", true);

        var symbol = d3.symbol()
            .type(d3.symbolTriangle)
            .size(200);

        let pointerY = wheel.radius - 10;
        wheel.base.append("path")
            .attr('d', symbol)
            .attr('transform', 'rotate(180) translate(0, ' + pointerY + ')');

    },

    rotate: function (slot = 0, callback=function(){}) {
        let arcLength = 360 / wheel.data.length;
        let slotArcCenter = arcLength * slot + arcLength / 2;

        wheel.newAngle = 1440 - slotArcCenter;
        let textAngle = 360 - slotArcCenter;

        function rotTween() {
            let i = d3.interpolate(wheel.oldAngle % 360, wheel.newAngle);
            return function (t) {
                return "rotate(" + i(t) + ")";
            };
        }

        if(debug){
            let sectorDuration = 0;
            let textDuration = 0;
        } else {
            let sectorDuration = 3000;
            let textDuration = 2000;
        }

        wheel.g.transition()
            .attrTween("transform", rotTween)
            .duration(function(){
                return (debug) ? 0 : 3000;
            })
            .on("end", function () {
                wheel.oldAngle = wheel.newAngle;
                callback();
            });

        wheel.text.transition()
            .attr("transform", function (dd) {
                let ctr = wheel.label.centroid(dd);
                return "translate(" + ctr + ") rotate(" + (-textAngle) + ")";
            })
            .duration(function(){
                return (debug) ? 0 : 2000;
            });
    },

    startTimer: function(duration=20){
        wheel.timerText.text("20");
        wheel.timer = d3.interval(function(elapsed){
            wheel.timerText.transition()
                .duration(750)
                .text(duration - Math.floor(elapsed/1000));
            if (elapsed > duration * 1000){
                wheel.timer.stop();
                wheel.timerCircle.attr("fill", "red");
            }
        }, 1000)
    },

    stopTimer: function(){
        wheel.timer.stop();
    },

    resetTimer: function(){
        wheel.stopTimer();
        wheel.timerText.text("\uf017");
        wheel.timerCircle.attr("fill", "#adc9e2");
    }
}

let board = {
    setActiveClue: function(column, row){
        $("#t_" + column + "_" + row).toggleClass("current-clue", true);
    },

    setValidClue: function(column, row){
        $("#t_" + column + "_" + row).toggleClass("current-clue", false);
        $("#t_" + column + "_" + row).toggleClass("valid-clue", true);
    },

    setInvalidClue: function (column, row) {
        $("#t_" + column + "_" + row).toggleClass("current-clue", false);
        $("#t_" + column + "_" + row).toggleClass("invalid-clue", true);
    },
    removeStyle: function(column, row){
        $("#t_" + column + "_" + row).toggleClass("current-clue", false);
        $("#t_" + column + "_" + row).toggleClass("valid-clue", false);
        $("#t_" + column + "_" + row).toggleClass("invalid-clue", false);
    }

}

let dialog = {
    show: function (title, text) {
        $("#dialog").attr("title", title)
            .html("<p>" + text + "</p>");

        $("#dialog").dialog({
            modal: true,
            buttons: [{
                text: "OK",
                click: function () {
                    $(this).dialog("close");
                }
            }],
            close: function (event, ui) {
                wheel.rotate();
            }
        });
    },

    endRound: function () {
        let title = "Round Complete";
        let roundNumber = game.currentRound + 1;
        let text = "Round " + roundNumber + " is complete.<br><br>"

        let leaders = game.getLeaderBoard();
        for (let i = 0; i < leaders.length; i++) {
            text += leaders[i].name + ": " + leaders[i].totalScore + "<br>";
        }
        dialog.show(title, text);
    },

    endGame: function () {
        let title = "Game Complete";
        let roundNumber = game.currentRound + 1;
        let text = "Game is complete.<br><br>"

        let leaders = game.getLeaderBoard();
        for (let i = 0; i < leaders.length; i++) {
            text += leaders[i].name + ": " + leaders[i].totalScore + "<br>";
        }
        dialog.show(title, text);
    }
}

function displayClue(){
    $(".spin-phase").hide();
    $(".answer-phase").show();
    
    playPanel.deductClue();

    let clue = round.currentClue;
    let points = clue.points;
    let category = round.board.getCategory(clue.column).name;

    board.setActiveClue(clue.column, clue.row);
    clueText.write(category + " (" + points +")\n" + clue.question);

    wheel.startTimer();
}

function switchPlayer(){
    // end current players turn and toggle UI
    player.endTurn(round.currentPlayerID);
    round.endTurn();
    player.startTurn(round.currentPlayerID);
}

function hidePanels(){
    $(".edit-panel").hide();
    $(".play-panel").hide();
    $(".spin-phase").hide();
    $(".answer-phase").hide();
    $(".validate-phase").hide();
    $(".token-phase").hide();
}

function download(filename, text) {
    // source: https://goo.gl/VWW2sT
    var element = document.createElement('a');
    element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
    element.setAttribute('download', filename);
    element.style.display = 'none';
    document.body.appendChild(element);

    element.click();
    document.body.removeChild(element);
}

class Alert {
    static success(msg) {
        let a = $("#alert");
        a.removeClass("alert-danger alert-success alert-warning")
            .addClass("alert-success");
        a.find("#alert-text").empty().text(msg);
        a.show();
    };

    static danger(msg) {
        let a = $("#alert");
        a.removeClass("alert-danger alert-success alert-warning")
            .addClass("alert-danger");
        a.find("#alert-text").empty().html(msg);
        a.show();
    };

    static warning(msg) {
        let a = $("#alert");
        a.removeClass("alert-danger alert-success alert-warning")
            .addClass("alert-warning");
        a.find("#alert-text").empty().html(msg);
        a.show();
    };

    static hide(ms) {
        // set milliseconds to delay hide
        if (ms) {
            setTimeout(function () { $("#alert").hide("slow") }, ms);
        } else {
            $("#alert").hide();
        }
    };
}