var host = 'http://localhost:8082';
if (location.hostname === "www.hanjie-star.com") {
    host = 'https://static.hanjie-star.com';
} else if (location.hostname === "beta.hanjie-star.com") {
    host = 'https://beta.static.hanjie-star.com';
}

var config = {
    scene: {
        size: {
            width: 1110,
            height: Math.max(1000, window.innerHeight),
            scale: 0.975,
            margin: 5
        },
        viewPort: {
            width: 1110,
            height: Math.max(1000, window.innerHeight),
        },
        background: {
            color: 0xffffff
        },
        loadingId: 'puzzle-loading',
        startId: 'puzzle-play-start',
        addId: 'puzzle-ad',
        attachedAd: true
    },
    grid: {
        zoom: {
            step: 0.05,
            minimum: 0.20,
            maximum: 1.5
        },
        cell: {
            size: 40,
            groupSize: 5,
            bordered: false,
            highlightTint: 0xfd8504,
        },
        border: {
            width: 1,
            color: 0x292929
        },
        line: {
            width: {
                thin: 1,
                bold: 6
            },
            color: {
                thin: 0xa9a9a9,
                bold: 0xa9a9a9
            }
        },
        background: {
            color: 0xffffff,
            alternateColor: [0xffffff, 0xfffdf9, 0xfef7e6, 0xf9eccc, 0xf7dea0, 0xffd672],
            contrastLevel: 3
        },
        hints: {
            font: '32px Nunito',
            style: 'bold',
            color: '#000000',
            disabledColor: '#cccccc',
            highlightedColor: '#ffffff',
            highlightedAndDisabledColor: '#ffb885',
            highlightedBackgroundColor: 0xeb4e00,
            highlightedBackgroundAlpha: 1.0,
            disabledEnabled: true,
            colSpacing: 15,
            rowSpacing: 20,
        },
        extend: {
            lessColor: 0xff0000,
            moreColor: 0xfbbc09,
            label: {
                font: {
                    size: '20',
                    family: 'Nunito',
                    style: 'bold'
                },
                color: 'black'
            },
            handle: {
                margin: 37
            },
            hints: {
                margin: 70
            }
        },
        size: {
            minimum: {
                rows: 5,
                cols: 5
            },
            maximum: {
                rows: 40,
                cols: 40
            }
        },
        draggable: true,
        highlightRowAndCol: true,
    },
    preview: {
        enabled: true,
        fixed: false,
        scale: 2.0,
        cell: {
            size: 2,
            color: 0x000000
        },
        border: {
            width: 1,
            color: 0x000000
        },
        background: {
            color: 0xffffff
        },
    },
    timer: {
        textStyle: {
            fontFamily: 'Nunito',
            fontSize: '50px',
            fontStyle: 'bold',
            color: '#696969'
        }
    },
    counter: {
        font: '26px Nunito',
        fontStyle: 'bold',
        backgroundColor: 0xFBBC09,
    },
    buttons: {
        size: {
            width: 290,
            height: 50,
            maxScale: 0.85,
            iconScale: 0.85,
        },
        backgroundColor: 0xFBBC09,
        secondaryBackgroundColor: 0x11A0F3,
        toggledBackgroundColor: 0xC17805,
        disabledBackgroundColor: 0xDEDEDE,
        textStyle: {
            fontFamily: 'Nunito',
            fontSize: '24px',
            fontStyle: 'bold',
            color: '#ffffff'
        },
        counter: {
            backgroundColor: 0xFFFFFF,
            size: 11,
            textStyle: {
                fontFamily: 'Nunito',
                fontSize: '15px',
                fontStyle: 'bold',
                color: '#C17805'
            }
        },
        frames: {
            settings: 0,
            check: 1,
            save: 2,
            reset: 3,
            historyBack: 4,
            historyForward: 5,
            helpRevealSquare: 6,
            helpRevealRowOrCol: 7,
            helpFixSquares: 8,
            validateTempCells: 9,
            deleteTempCells: 10,
            cellSelected: 11,
            cellUnselected: 12,
            cellSelectedTemp: 13,
            cellUnselectedTemp: 14,
            pause: 15,
            zoomOut: 16,
            zoomIn: 17,
            image: 18,
            invert: 19,
            logical: 20,
            toggle: 21,
            plaintoggle: 22,
        }
    },
    help: {
        defaultBackgroundColor: 0x11A0F3,
        okBackgroundColor: 0x32CD32,
        text: {
            height: 100,
            margin: 5,
            y: 230,
            style: {
                fontFamily: 'Nunito',
                fontSize: '23px',
                color: '#ffffff',
                align: 'center',
            },
        },
        mode: {
            revealRowOrCol: 1,
            revealSquare: 2,
            fixSquares: 3,
            save: 4,
            reset: 5,
        }
    },
    history: {
        count: 10
    },
    settings: {
        id: 'puzzle-settings',
        colorPickerId: 'color-picker',
    },
    state: {
        unknown: '0',
        selected: '1',
        selectedTemp: '2',
        unselected: '3',
        unselectedTemp: '4'
    },
    frame: {
        cell: {
            selected: 0,
            selectedTemp: 1,
            unselected: 2,
            unselectedTemp: 3,
            help: 4,
            selectedRevealed: 5,
            unselectedRevealed: 6,
        }
    },
    cellMode: {
        none: 0, // no cell can be changed
        normal: 1, // the normal mode for the cells
        help: 2, // select a cell to reveal its solution
        reveal: 3, // reveal some cells
    }
};

//----- Helpers functions.
function getScene() {
    return window.gameUI.scene.getScenes()[0];
}

function setSceneSize(width, height) {
    var previousWidth = config.scene.size.width;
    var previousHeight = config.scene.size.height;

    config.scene.size.width = width * config.scene.size.scale;
    config.scene.size.height = height*1.75 * config.scene.size.scale;
    config.scene.viewPort.width = config.scene.size.width;
    config.scene.viewPort.height = height * config.scene.size.scale;

    return config.scene.size.width != previousWidth || config.scene.size.height != previousHeight
}

function showGameUI() {
    // Update the ad.
    if (config.scene.attachedAd) {
        window.puzzleAd.appendTo(window.puzzleAdTo);
    }

    // Show the game.
    $('body > *').hide();
    window.puzzleContainer.show();
    window.puzzleAdTo.show();

    // Update the amount of pause seconds.
    if (!_.isUndefined(window.gameConfig) && !_.isUndefined(window.gameConfig.pauseDate)) {
        window.gameConfig.pause += parseInt((Date.now() - window.gameConfig.pauseDate)/1000, 10);
        window.gameConfig.pauseDate = undefined;
    
        getScene().stopped = false;
        getScene().startTimer();
    }

    $(window).scrollTop(0);
}

function hideGameUI() {
    // Update the ad.
    if (config.scene.attachedAd) {
        window.puzzleAd.appendTo(window.puzzleAdFrom);
    }

    // Hide the game.
    $('body > *').show();
    $('body > .modal').hide();
    window.puzzleContainer.hide();
    window.puzzleAdTo.hide();

    // Update the amount of pause seconds.
    window.gameConfig.pauseDate = Date.now();
    getScene().stopped = true;

    var $top = $('#' + config.scene.startId);
    if ($top.length > 0) {
        $(window).scrollTop($top.offset().top);
    }

    // Fix : hide the Adinplay panel.
    $('#cmpbox').hide();
}

function showGameLoading() {
    window.puzzleLoading.appendTo($('body'));
}

function hideGameLoading() {
    window.puzzleLoading.remove();
}

//------------------
class GameHistory {
    constructor(count) {
        this.backward = [];
        this.forward = [];
        this.current = null;
        this.count = count;
    }

    startNewEntry(state) {
        this.current = {state: state, cells: []};
        this.forward = [];
    }

    saveCurrentEntry() {
        if (_.isUndefined(this.current) || this.current.cells === null || _.isUndefined(this.current.cells) || this.current.cells.length == 0) {
            return;
        }

        this.addBackwardEntry(this.current);
        this.current = null;
    }

    addDataToCurrentEntry(x, y, state) {
        if (_.isUndefined(this.current) || this.current.cells === null || _.isUndefined(this.current.cells)) {
            return;
        }

        if (!this.current.cells.some(e => e.x === x && e.y === y)) {
            this.current.cells.push({x: x, y: y, s: state});
        }
    }

    addBackwardEntry(entry) {
        this.backward.push(entry);
        if (this.backward.length > this.count) {
            this.backward.shift();
        }
    }

    getBackwardEntry() {
        return this.backward.pop();
    }

    addForwardEntry(entry) {
        this.forward.push(entry);
    }

    getForwardEntry() {
        return this.forward.pop();
    }

    hasBackwardEntries() {
        return this.backward.length > 0;
    }

    hasForwardEntries() {
        return this.forward.length > 0;
    }
}

//------------------
class ButtonImage extends Phaser.GameObjects.Sprite {
    constructor(scene, x, y, texture, toggleEnabled, counterEnabled, frame) {
        super(scene, x, y, texture, frame);
        scene.add.existing(this);

        this.toggleEnabled = toggleEnabled;
        this.toggled = false;
        this.enabled = false;
        this.enable();

        if (counterEnabled) {
            var counterX = x + this.width/2 - this.width/10;
            var counterY = y + this.height/2 - this.height/10;
            this.counterBackground = scene.add.circle(counterX, counterY, 20, config.counter.backgroundColor);
            this.counterBackground.setOrigin(0.5, 0.5);
            this.counterText = scene.add.text(counterX, counterY, "0", {font: config.counter.font});
            this.counterText.setFontStyle(config.counter.fontStyle);
            this.counterText.setOrigin(0.5, 0.5);
            this.counterText.setVisible(true);
        }

        this.on('pointerover', function(e) {
            if (this.toggled || !this.enabled) {
                return;
            }

            this.setFrame(this.hoverFrame);
        });
        this.on('pointerout', function(e) {
            this.updateToggleFrame();
        });
        this.on('pointerdown', function(e) {
            if (!this.enabled) {
                return;
            }

            this.setFrame(this.activeFrame);
        });
        this.on('pointerup', function(e) {
            if (!this.enabled) {
                return;
            }

            if (this.toggleEnabled) {
                this.toggle();
            } else {
                this.setFrame(this.hoverFrame);
            }
    
            if (this.action !== undefined) {
                this.action(e, this.toggled);
            }
        });
    }

    setScale(x, y) {
        var r = super.setScale(x, y);

        var counterX = this.x + this.displayWidth/2 - this.displayWidth/10;
        var counterY = this.y + this.displayHeight/2 - this.displayHeight/10;
        if (!_.isUndefined(this.counterBackground)) {
            this.counterBackground.setScale(x, y);
            this.counterBackground.setX(counterX);
            this.counterBackground.setY(counterY);
        }
        if (!_.isUndefined(this.counterText)) {
            this.counterText.setScale(x, y);
            this.counterText.setX(counterX);
            this.counterText.setY(counterY);
        }

        return r
    }

    setNormalFrame(frame) {
        this.normalFrame = frame;
    }

    setHoverFrame(frame) {
        this.hoverFrame = frame;
    }

    setActiveFrame(frame) {
        this.activeFrame = frame;
    }

    setDisabledFrame(frame) {
        this.disabledFrame = frame;
    }

    setAction(action) {
        this.action = action;
    }

    enable() {
        this.enabled = true;
        this.setInteractive({ cursor: 'pointer' });
        this.updateToggleFrame();
    }

    disable() {
        this.enabled = false;
        this.disableInteractive();
        this.setFrame(this.disabledFrame);
    }

    toggle() {
        this.toggled = !this.toggled;
        this.updateToggleFrame();
    }

    untoggle() {
        this.toggled = false;
        this.updateToggleFrame();
    }

    updateToggleFrame() {
        if (!this.enabled) {
            this.setFrame(this.disabledFrame);
        } else if (this.toggled) {
            this.setFrame(this.activeFrame);
        } else {
            this.setFrame(this.normalFrame);
        }
    }

    updateCounter(text) {
        if (!_.isUndefined(this.counterText)) {
            this.counterText.setText(text);
        }
    }

    show() {
        this.enable();
        this.setActive(true).setVisible(true);

        if (!_.isUndefined(this.counterText)) {
            this.counterText.setActive(true).setVisible(true);
            this.counterBackground.setActive(true).setVisible(true);
        }
    }

    hide() {
        this.disable();
        this.setActive(false).setVisible(false);

        if (!_.isUndefined(this.counterText)) {
            this.counterText.setActive(false).setVisible(false);
            this.counterBackground.setActive(false).setVisible(false);
        }
    }
}

//------------------
class BButton extends Phaser.GameObjects.Container {
    constructor(scene, x, y, children, t, texture, frame, counterEnabled = false, secondary = false) {
        super(scene, x, y, children);
        scene.add.existing(this);

        var hasIcon = (!_.isUndefined(texture) && texture.length > 0);
        var hasText = (!_.isUndefined(t) && t.length > 0);
        var width = (hasText ? config.buttons.size.width : config.buttons.size.height);
        var height = config.buttons.size.height;

        this.setSize(width, height);
        
        this.background = scene.add.graphics();
        this.background.fillStyle(secondary ? config.buttons.secondaryBackgroundColor : config.buttons.backgroundColor, 1.0);
        this.background.fillRoundedRect(-width/2, -height/2, width, height, height/2);
        this.addAt(this.background, 0);
        
        this.toggledBackground = scene.add.graphics();
        this.toggledBackground.fillStyle(config.buttons.toggledBackgroundColor, 1.0);
        this.toggledBackground.fillRoundedRect(-width/2, -height/2, width, height, height/2);
        this.addAt(this.toggledBackground, 1);
        
        this.disabledBackground = scene.add.graphics();
        this.disabledBackground.fillStyle(config.buttons.disabledBackgroundColor, 1.0);
        this.disabledBackground.fillRoundedRect(-width/2, -height/2, width, height, height/2);
        this.addAt(this.disabledBackground, 2);

        if (hasIcon) {
            this.icon = scene.add.sprite(-this.width/2 + config.buttons.size.height/2, 0, texture, frame);
            this.addAt(this.icon, 10);
            this.icon.setScale(config.buttons.size.iconScale);
        }

        if (hasText) {
            var text = scene.add.text(0, 0, t, config.buttons.textStyle);
            text.setX(-text.width/2 + (hasIcon ? this.icon.width/2 : 0));
            text.setY(-text.height/2);
            this.addAt(text, 10);
        }

        if (counterEnabled) {
            var x = (hasIcon ? this.icon.x + this.icon.width/2 : 0);
            var y = (hasIcon ? this.icon.y + this.icon.height/4 : 0);

            this.counterBackground = scene.add.circle(x, y, config.buttons.counter.size, config.buttons.counter.backgroundColor);
            this.addAt(this.counterBackground, 20);

            this.counterText = scene.add.text(0, 0, '0', config.buttons.counter.textStyle);
            this.counterText.setX(x - this.counterText.width);
            this.counterText.setY(y - this.counterText.height/2);
            this.addAt(this.counterText, 21);
        }

        this.enable();
        this.untoggle();

        this.on('pointerover', function(e) {
            if (!this.enabled) {
                return;
            }

            this.setAlpha(0.5);
        });
        this.on('pointerout', function(e) {
            this.setAlpha(1.0);
        });
        this.on('pointerup', function(e) {
            if (!this.enabled) {
                return;
            }
    
            if (this.action !== undefined) {
                this.action(e, this.toggled);
            }
        });
    }

    setAction(action) {
        this.action = action;
        return this;
    }

    enable() {
        this.enabled = true;
        this.setInteractive({ cursor: 'pointer' });
        this.updateState();
        return this;
    }

    disable() {
        this.enabled = false;
        this.disableInteractive();
        this.updateState();
        return this;
    }

    show() {
        this.enable().setActive(true).setVisible(true);
        return this;
    }

    hide() {
        this.disable().setActive(false).setVisible(false);
        return this;
    }

    toggle() {
        this.toggled = !this.toggled;
        this.updateState();
        return this;
    }

    untoggle() {
        this.toggled = false;
        this.updateState();
        return this;
    }

    updateState() {
        this.disabledBackground.setVisible(!this.enabled);
        this.background.setVisible(!this.toggled && this.enabled);
        this.toggledBackground.setVisible(this.toggled && this.enabled);

        if (!_.isUndefined(this.icon)) {
            this.icon.setAlpha(this.enabled ? 1.0 : 0.5);
        }

        return this;
    }

    updateCounterText(count) {
        this.counterText.setText(count);
        this.counterText.setX((!_.isUndefined(this.icon) ? this.icon.x + this.icon.width/2 : 0) - this.counterText.width/2);
    }
}

//------------------
class GameScene extends Phaser.Scene {
    constructor() {
        super("game");

        // Initialize the constants.
        this.cellSize = config.grid.cell.size + config.grid.line.width.thin;
        this.altBGSize = (config.grid.cell.size*config.grid.cell.groupSize + config.grid.line.width.thin*(config.grid.cell.groupSize-1));

        // Fix the solution format if needed.
        var solutionLength = gameConfig.cols.count * gameConfig.rows.count;
        if (gameConfig.createMode || gameConfig.testMode) {
            gameConfig.solution = this.decodeSolution();
        }

        if (gameConfig.solution.length != solutionLength) {
            gameConfig.solution = '';
            for (var i=0; i<solutionLength; i++) {
                gameConfig.solution += '0';
            }
        }

        if (gameConfig.createMode) {
            gameConfig.cols.hints = [];
            for (var x=0; x<gameConfig.cols.count; x++) {
                this.updateColHint(x);
            }
            gameConfig.rows.hints = [];
            for (var y=0; y<gameConfig.rows.count; y++) {
                this.updateRowHint(y);
            }
        } else {
            if (!_.isArray(gameConfig.rows.hints)) {
                gameConfig.rows.hints = gameConfig.rows.hints.split(';');
            }
            if (!_.isArray(gameConfig.cols.hints)) {
                gameConfig.cols.hints = gameConfig.cols.hints.split(';');
            }
        }

        this.helpModeEnabled = false

        // Initialize the grid object.
        this.grid = {
            alternateBackgrounds: [],
            cells: [],
            lines: {
                cols: [],
                rows: []
            },
            hints: {
                cols: [],
                rows: []
            },
            preview: {},
            handles: {}
        };

        // Initialize the extend grid object.
        this.extendGrid = {
            horizontal: false,
            count: 0,
            startDragging: 0
        };

        // Initialize the selection
        this.selection = {
            state: config.state.selected,
            invert: false, // true if left click, false if right click. If invert is true, the inverse state is applied.
            cell: {
                x: -1,
                y: -1,
                state: config.state.unknown,
                applying: false
            }
        }

        // Initialize the history.
        this.history = new GameHistory(config.history.count);

        // The unused hints.
        this.unusedHints = [];

        // Set up the settings form.
        this.settingsForm = $('#' + config.settings.id);

        var form = this.settingsForm;
        var scene = this;
        this.settingsForm.off('submit').on('submit', function(e) {
            e.preventDefault();

            var settings = getPuzzleSettings();

            if (config.grid.cell.bordered != settings.borderedCells) {
                config.grid.cell.bordered = settings.borderedCells;

                for (var y = 0; y < gameConfig.rows.count; y++) {
                    for (var x = 0; x < gameConfig.cols.count; x++) {
                        var cell = scene.cellAt(x, y);
                        if (cell.visible) {
                            scene.updateCellBorder(cell);
                        }
                    }
                }
            }

            if (config.preview.fixed != settings.fixedPreview || config.preview.scale != settings.previewScale) {
                config.preview.fixed = settings.fixedPreview;
                config.preview.scale = settings.previewScale;
                scene.updatePreviewPosition();
            }

            if (config.grid.hints.disabledEnabled != settings.coloredHints) {
                config.grid.hints.disabledEnabled = settings.coloredHints;

                for (var i = 0; i < scene.grid.hints.cols.length; i++) {
                    var texts = scene.grid.hints.cols[i];
                    for (var j=0; j<texts.length; j++) {
                        scene.colorHintText(texts[j], false);
                    }
                }
        
                for (var i = 0; i < scene.grid.hints.rows.length; i++) {
                    var texts = scene.grid.hints.rows[i];
                    for (var j=0; j<texts.length; j++) {
                        scene.colorHintText(texts[j], false);
                    }
                }
            }

            if (config.grid.background.contrastLevel != settings.backgroundContrast) {
                config.grid.background.contrastLevel = settings.backgroundContrast;

                for (var i = 0; i < scene.grid.alternateBackgrounds.length; i++) {
                    scene.grid.alternateBackgrounds[i].setFillStyle(config.grid.background.alternateColor[config.grid.background.contrastLevel]);
                }
            }

            config.grid.highlightRowAndCol = settings.highlightRowAndCol;

            config.grid.draggable = settings.draggableGrid;
            scene.updateDraggableGrid();
            
            form.modal('hide');
            scene.saveSettings(settings);
        });
    }

    preload() {
        this.load.spritesheet('cells', host + '/images/game/cells.png', {frameWidth: 40, frameHeight: 40});
        this.load.spritesheet('buttons', host + '/images/game/buttons2.1.png', {frameWidth: 50, frameHeight: 50});
        this.load.image('handle_v', host + '/images/game/handle_v.png');
        this.load.image('handle_h', host + '/images/game/handle_h.png');
    }

    create() {
        this.stopped = false;
        this.initGrid();
        this.updateGridShape();
        this.updatePreviewShape();
        this.addHintTexts();
        this.centerGrid();
        this.updateGridCells();
        this.updateHistoryButtons();
        this.updateZoomButtons();
        this.updateTimer();
        this.startTimer();
        hideGameLoading();
    }

    // Event functions.
    highlightCell(e) {
        // Get the coordinates.
        var c = this.getXYFromEventPosition(e);
        var x = c.x;
        var y = c.y;

        if (x == this.selection.cell.x && y == this.selection.cell.y) {
            return;
        }

        // Tint the hints.
        if (x != this.selection.cell.x) {
            if (this.selection.cell.x >= 0 && this.selection.cell.x < gameConfig.cols.count && this.selection.cell.x < this.grid.hints.cols.length) {
                var theHints = this.grid.hints.cols[this.selection.cell.x];
                for (var i=0; i<theHints.length; i++) {
                    this.colorHintText(theHints[i], false);
                }

                this.updateCellsColorAtRow(this.selection.cell.x, false);
            }
            if (x >= 0 && x < gameConfig.cols.count && x < this.grid.hints.cols.length) {
                var theHints = this.grid.hints.cols[x];
                for (var i=0; i<theHints.length; i++) {
                    this.colorHintText(theHints[i], true);
                }
            }
        }

        if (y != this.selection.cell.y) {
            if (this.selection.cell.y >= 0 && this.selection.cell.y < gameConfig.rows.count && this.selection.cell.y < this.grid.hints.rows.length) {
                var theHints = this.grid.hints.rows[this.selection.cell.y];
                for (var i=0; i<theHints.length; i++) {
                    this.colorHintText(theHints[i], false);
                }

                this.updateCellsColorAtCol(this.selection.cell.y, false);
            }
            if (y >= 0 && y < gameConfig.rows.count && y < this.grid.hints.rows.length) {
                var theHints = this.grid.hints.rows[y];
                for (var i=0; i<theHints.length; i++) {
                    this.colorHintText(theHints[i], true);
                }
            }
        }

        // Save the current coordinates.
        this.selection.cell.x = x;
        this.selection.cell.y = y;

        // Color the cell.
        this.colorCell(false);

        // Highlight the other cells.
        this.updateCellsColorAtRow(x, true);
        this.updateCellsColorAtCol(y, true);

        // Update the highlighted backgrounds.
        var cell = this.cellAt(x, y);

        this.grid.highlightedBackgroundHintCol.setPosition(cell.x - this.cellSize/2.0, this.grid.highlightedBackgroundHintCol.y);
        this.grid.highlightedBackgroundHintCol.setVisible(true);

        this.grid.highlightedBackgroundHintRow.setPosition(this.grid.highlightedBackgroundHintRow.x, cell.y - this.cellSize/2.0);
        this.grid.highlightedBackgroundHintRow.setVisible(true);

        if (config.grid.highlightRowAndCol) {
            cell.setAlpha(1.0);
            cell.clearTint();
        }
    }

    stopHighlightingCell(e) {
        if (this.selection.cell.x < 0 || this.selection.cell.y < 0) {
            return;
        }

        if (this.selection.cell.x < this.grid.hints.cols.length) {
            var theHints = this.grid.hints.cols[this.selection.cell.x];
            for (var i=0; i<theHints.length; i++) {
                this.colorHintText(theHints[i], false);
            }
            this.updateCellsColorAtRow(this.selection.cell.x, false);
        }
        if (this.selection.cell.y < this.grid.hints.rows.length) {
            var theHints = this.grid.hints.rows[this.selection.cell.y];
            for (var i=0; i<theHints.length; i++) {
                this.colorHintText(theHints[i], false);
            }
            this.updateCellsColorAtCol(this.selection.cell.y, false);
        }

        this.grid.highlightedBackgroundHintRow.setVisible(false);
        this.grid.highlightedBackgroundHintCol.setVisible(false);

        this.selection.cell.x = -1;
        this.selection.cell.y = -1;
    }

    colorCell(startApplying) {
        var x = this.selection.cell.x;
        var y = this.selection.cell.y;

        if (!this.selection.cell.applying || x<0 || y<0 || x>=gameConfig.cols.count || y>=gameConfig.rows.count) {
            return
        }

        var cellState = this.stateOfCell(x, y);
        if (startApplying) {
            var selectedState = this.selection.state;
            if (this.selection.invert) {
                switch (this.selection.state) {
                    case config.state.selected:
                        selectedState = config.state.unselected;
                        break;
                    case config.state.unselected:
                        selectedState = config.state.selected;
                        break;
                    case config.state.selectedTemp:
                        selectedState = config.state.unselectedTemp;
                        break;
                    case config.state.unselectedTemp:
                        selectedState = config.state.selectedTemp;
                        break;
                }
            }

            if (selectedState == cellState) {
                this.selection.cell.state = config.state.unknown;
            } else {
                this.selection.cell.state = selectedState;
            }

            if (!gameConfig.createMode) {
                this.history.startNewEntry(this.selection.cell.state);
            }
        }

        if (cellState != this.selection.cell.state) {
            var updateHintColors = false;
            if (!gameConfig.createMode) {
                var nextState = this.selection.cell.state;
                updateHintColors = (
                    ((cellState == config.state.selected || cellState == config.state.selectedTemp) && (nextState == config.state.unselected || nextState == config.state.unselectedTemp || nextState == config.state.unknown)) ||
                    ((cellState == config.state.unselected || cellState == config.state.unselectedTemp || cellState == config.state.unknown) && (nextState == config.state.selected || nextState == config.state.selectedTemp))
                );
            }

            this.updateCell(x, y, this.selection.cell.state);

            if (gameConfig.createMode) {
                this.updateColHint(x);
                this.updateRowHint(y);
                this.updateColHintText(x, true);
                this.updateRowHintText(y, true);
            } else {
                this.history.addDataToCurrentEntry(x, y, cellState);

                if (updateHintColors) {
                    this.updateColHintTextColor(x, true);
                    this.updateRowHintTextColor(y, true);
                }
            }
        }
    }

    startColoring(e) {
        // Don't modify the cells.
        if (gameConfig.cellMode == config.cellMode.none) {
            return;
        }

        // Help mode : select a cell to reveal its solution.
        if (this.helpModeEnabled  && this.helpType == config.help.mode.revealSquare) {
            this.clearHelpSelectedSquare();

            gameConfig.helpSelectedSquare = {
                x: this.selection.cell.x,
                y: this.selection.cell.y,
            };

            this.updateCell(this.selection.cell.x, this.selection.cell.y, config.state.selected, false, false);
            return;
        }

        // Normal mode.
        this.selection.cell.applying = true;
        if (e.buttons == 2) {
            this.selection.invert = true;
        }
        
        this.colorCell(true);
    }

    stopColoring(e) {
        if (this.selection.cell.applying) {
            this.selection.cell.applying = false;
            this.selection.cell.state = config.state.unknown;
            this.selection.invert = false;

            if (!gameConfig.createMode) {
                this.history.saveCurrentEntry();
                this.updateHistoryButtons();
            }
        }
    }

    validateTempCells(e) {
        var selectedCells = [];
        var unselectedCells = [];

        for (var y = 0; y < gameConfig.rows.count; y++) {
            for (var x = 0; x < gameConfig.cols.count; x++) {
                var state = this.stateOfCell(x, y);
                if (state == config.state.selectedTemp) {
                    selectedCells.push({x: x, y: y});
                } else if (state == config.state.unselectedTemp) {
                    unselectedCells.push({x: x, y: y});
                }
            }
        }

        if (selectedCells.length > 0) {
            this.history.startNewEntry(config.state.selected);
            for (var i = 0; i < selectedCells.length; i++) {
                var coords = selectedCells[i];
                this.updateCell(coords.x, coords.y, config.state.selected);
                this.history.addDataToCurrentEntry(coords.x, coords.y, config.state.selectedTemp);
            }
            this.history.saveCurrentEntry();
        }

        if (unselectedCells.length > 0) {
            this.history.startNewEntry(config.state.unselected);
            for (var i = 0; i < unselectedCells.length; i++) {
                var coords = unselectedCells[i];
                this.updateCell(coords.x, coords.y, config.state.unselected);
                this.history.addDataToCurrentEntry(coords.x, coords.y, config.state.unselectedTemp);
            }
            this.history.saveCurrentEntry();
        }

        this.updateHistoryButtons();
    }

    deleteTempCells(e) {
        this.history.startNewEntry(config.state.unknown);

        for (var y = 0; y < gameConfig.rows.count; y++) {
            for (var x = 0; x < gameConfig.cols.count; x++) {
                var state = this.stateOfCell(x, y);
                if (state == config.state.selectedTemp || state == config.state.unselectedTemp) {
                    this.updateCell(x, y, config.state.unknown);
                    this.history.addDataToCurrentEntry(x, y, state);
                }
            }

        }

        this.history.saveCurrentEntry();
        this.updateHistoryButtons();
        this.updateAllHintTexts();
    }

    historyBack(e) {
        var entry = this.history.getBackwardEntry();
        if (entry === undefined) {
            return;
        }

        for (var i = 0; i < entry.cells.length; i++) {
            var cell = entry.cells[i];
            this.updateCell(cell.x, cell.y, cell.s);
        }

        this.history.addForwardEntry(entry);
        this.updateHistoryButtons();
        this.updateAllHintTexts();
    }

    historyForward(e) {
        var entry = this.history.getForwardEntry();
        if (entry === undefined) {
            return;
        }

        for (var i = 0; i < entry.cells.length; i++) {
            var cell = entry.cells[i];
            this.updateCell(cell.x, cell.y, entry.state);
        }

        this.history.addBackwardEntry(entry);
        this.updateHistoryButtons();
        this.updateAllHintTexts();
    }

    createFromImage(e) {
        if (!_.isUndefined(gameConfig.createFromImage)) {
            gameConfig.createFromImage(this.buttonSave);
        }
    }

    clearCells(e) {
        for (var y = 0; y < gameConfig.rows.count; y++) {
            for (var x = 0; x < gameConfig.cols.count; x++) {
                var state = this.stateOfCell(x, y);
                if (state != config.state.unknown) {
                    this.updateCell(x, y, config.state.unknown);
                }
            }

        }

        for (var x = 0; x < gameConfig.rows.count; x++) {
            this.updateRowHint(x);
            this.updateRowHintText(x, false);
        }
            
        for (var x = 0; x < gameConfig.cols.count; x++) {
            this.updateColHint(x);
            this.updateColHintText(x, false);
        }
    }

    invertCells(e) {
        for (var y = 0; y < gameConfig.rows.count; y++) {
            for (var x = 0; x < gameConfig.cols.count; x++) {
                var state = this.stateOfCell(x, y);
                var isFilled = (state == config.state.selected || state == config.state.selectedTemp);
                this.updateCell(x, y, isFilled ? config.state.unknown : config.state.selected);
            }

        }

        for (var x = 0; x < gameConfig.rows.count; x++) {
            this.updateRowHint(x);
            this.updateRowHintText(x, false);
        }
            
        for (var x = 0; x < gameConfig.cols.count; x++) {
            this.updateColHint(x);
            this.updateColHintText(x, false);
        }
    }

    checkLogical(e) {
        if (!_.isUndefined(gameConfig.checkLogical)) {
            gameConfig.checkLogical(this.buttonCheckLogical);
        }
    }

    saveCreation(e) {
        if (!_.isUndefined(gameConfig.savePuzzle)) {
            gameConfig.savePuzzle(this.buttonSave);
        }
    }

    saveSettings(settings) {
        if (!_.isUndefined(gameConfig.saveSettings)) {
            gameConfig.saveSettings(settings);
        }
    }

    resetGame(e) {
        if (_.isUndefined(gameConfig.resetGame)) {
            return;
        }
        
        gameConfig.resetGame();
    }

    saveGame(e) {
        if (_.isUndefined(gameConfig.saveGame)) {
            return;
        }

        var s = this.encodeSavedSolution();
        var t = getElapsedSeconds();
        var p = getPausedSeconds();
        var button = this.buttonSave;
        button.disable();
        gameConfig.saveGame(s, t, p, function(){
            button.enable();
        });
    }

    checkGame(e) {
        if (_.isUndefined(gameConfig.checkGame)) {
            return;
        }

        var s = this.encodeSolution();
        var p = getPausedSeconds();
        var button = this.buttonCheck;
        button.disable();
        gameConfig.checkGame(s, p, function(){
            button.enable();
        });
    }

    showSettings(e) {
        this.settingsForm.appendTo($('body')).modal('show');
    }

    zoomOut(e) {
        this.zoom(e, true);
    }

    zoomIn(e) {
        this.zoom(e, false);
    }

    zoom(e, out) {
        var scale = this.grid.container.scaleX;

        if (out) {
            scale -= config.grid.zoom.step;
            if (scale < config.grid.zoom.minimum) {
                scale = config.grid.zoom.minimum;
            }
        } else {
            scale += config.grid.zoom.step;
            if (scale > config.grid.zoom.maximum) {
                scale = config.grid.zoom.maximum;
            }
        }

        scale = Math.round(scale*100)/100;

        var x = this.grid.container.x;
        var y = this.grid.container.y;
        var diffX = Math.abs(this.grid.container.displayWidth - (this.grid.container.width * scale));
        var diffY = Math.abs(this.grid.container.displayHeight - (this.grid.container.height * scale));
/*
        if (out) {
            x += (diffX/5);
            y += (diffY/5);
        } else {
            x -= (diffX/5);
            y -= (diffY/5);
        }
*/
        var scene = this;
        this.tweens.add({
            targets: this.grid.container,
            scaleX: scale,
            scaleY: scale,
            x: x,
            y: y,
            ease: 'Sine.easeInOut',
            duration: 150,
            onComplete: function (t) { t.stop(); scene.updateZoomButtons(); }
        });
    }

    stopExtendingGrid(e) {
        if (this.extendGrid.rect === undefined) {
            return;
        }

        var count = this.extendGrid.count;
        this.extendGrid.rect.setVisible(false);
        this.extendGrid.label.setVisible(false);
        this.extendGrid.count = 0;

        if (count == 0) {
            this.updateHandles();
            return;
        }

        var horizontal = (this.extendGrid.direction == 'l' || this.extendGrid.direction == 'r');
        var toLeft = (this.extendGrid.direction == 'l');
        var toTop = (this.extendGrid.direction == 't');

        // Update the model.
        if (horizontal) {
            var initialCountOfCols = gameConfig.cols.count;
            gameConfig.cols.count += count;

            if (count > 0) {
                var solutionExtend = '';
                for (var i=0; i<count; i++) {
                    if (toLeft) {
                        gameConfig.cols.hints.unshift('0');
                    } else {
                        gameConfig.cols.hints.push('0');
                    }
                    solutionExtend += '0';
                }

                var p = (toLeft ? 1 : 0);
                for (var i=gameConfig.rows.count; i>0; i--) {
                    var index = (i - p) * initialCountOfCols;
                    gameConfig.solution = gameConfig.solution.substring(0, index) + solutionExtend + gameConfig.solution.substring(index);
                }
            } else {
                var total = Math.abs(count);
                for (var i=0; i<total; i++) {
                    if (toLeft) {
                        gameConfig.cols.hints.shift();
                    } else {
                        gameConfig.cols.hints.pop();
                    }
                }

                var p = (toLeft ? 0 : 1);
                for (var i=0; i<gameConfig.rows.count; i++) {
                    var index = (i + p) * gameConfig.cols.count;
                    gameConfig.solution = gameConfig.solution.substring(0, index) + gameConfig.solution.substring(index + total);
                    this.updateRowHint(i);
                }
            }
        }
        else {
            gameConfig.rows.count += count;

            if (count > 0) {
                for (var i=0; i<count; i++) {
                    if (toTop) {
                        gameConfig.rows.hints.unshift('0');
                    } else {
                        gameConfig.rows.hints.push('0');
                    }
                }

                for (var i=0; i<(gameConfig.cols.count*count); i++) {
                    if (toTop) {
                        gameConfig.solution = '0' + gameConfig.solution;
                    } else {
                        gameConfig.solution += '0';
                    }
                }
            } else {
                var total = Math.abs(count);
                for (var i=0; i<total; i++) {
                    if (toTop) {
                        gameConfig.rows.hints.shift();
                    } else {
                        gameConfig.rows.hints.pop();
                    }
                }

                if (toTop) {
                    gameConfig.solution = gameConfig.solution.substring(gameConfig.cols.count*total);
                } else {
                    gameConfig.solution = gameConfig.solution.substring(0, gameConfig.rows.count*gameConfig.cols.count);
                }
                for (var i=0; i<gameConfig.cols.count; i++) {
                    this.updateColHint(i);
                }
            }
        }

        // Update the UI.
        this.updateGridShape();
        this.updatePreviewShape();

        var total = Math.abs(count);

        // Update the hints.
        if (count < 0) {
            if (horizontal) {
                this.hideColHintTexts(total, toLeft);
                for (var i=0; i<gameConfig.rows.count; i++) {
                    this.updateRowHintText(i);
                }
            } else {
                this.hideRowHintTexts(total, toTop);
                for (var i=0; i<gameConfig.cols.count; i++) {
                    this.updateColHintText(i);
                }
            }
        } else {
            if (horizontal) {
                this.showColHintTexts(count, toLeft);
            } else {
                this.showRowHintTexts(count, toTop);
            }
        }

        // Update the cells.
        if (toLeft || toTop) {
            this.updateGridCells();
        } else if (count < 0) {
            // Reset and hide the cells.
            if (horizontal) {
                var begin = gameConfig.cols.count;
                var end = begin + total;
                for (var x=begin; x<end; x++) {
                    for (var y=0; y<gameConfig.rows.count; y++) {
                        this.updateCell(x, y, config.state.unknown, false);
                    }
                }
            } else {
                var begin = gameConfig.rows.count;
                var end = begin + total;
                for (var y=begin; y<end; y++) {
                    for (var x=0; x<gameConfig.cols.count; x++) {
                        this.updateCell(x, y, config.state.unknown, false);
                    }
                }
            }
        }
    }

    updateSelectedState(state) {
        this.selection.state = state;

        this.buttonCellSelected.untoggle();
        this.buttonCellSelectedTemp.untoggle();
        this.buttonCellUnselected.untoggle();
        this.buttonCellUnselectedTemp.untoggle();

        switch (state) {
            case config.state.selected:
            this.buttonCellSelected.toggle();
            break;
            case config.state.selectedTemp:
            this.buttonCellSelectedTemp.toggle();
            break;
            case config.state.unselected:
            this.buttonCellUnselected.toggle();
            break;
            case config.state.unselectedTemp:
            this.buttonCellUnselectedTemp.toggle();
            break;
        }
    }

    updateSavesCounter(count) {
        if (_.isUndefined(this.buttonSave) || _.isUndefined(this.buttonSave.counterText)) {
            return
        }

        this.buttonSave.updateCounterText(count);
    }

    updateGameHelpRevealRowOrColCounter(count) {    
        if (_.isUndefined(this.buttonHelpRevealRowOrCol) || _.isUndefined(this.buttonHelpRevealRowOrCol.counterText)) {
            return
        }

        this.buttonHelpRevealRowOrCol.updateCounterText(count);
    }

    updateGameHelpRevealSquareCounter(count) {
        if (_.isUndefined(this.buttonHelpRevealSquare) || _.isUndefined(this.buttonHelpRevealSquare.counterText)) {
            return
        }

        this.buttonHelpRevealSquare.updateCounterText(count);
    }

    updateGameHelpFixSquaresCounter(count) {
        if (_.isUndefined(this.buttonHelpFixSquares) || _.isUndefined(this.buttonHelpFixSquares.counterText)) {
            return
        }

        this.buttonHelpFixSquares.updateCounterText(count);
    }

    useHelpFixSquares() {
        if (_.isUndefined(gameConfig.useHelpFixSquares)) {
            return;
        }

        var s = this.encodeSavedSolution();
        var buttonValidate = this.buttonHelpModeValidate;
        var buttonCancel = this.buttonHelpModeCancel;
        var buttonHelp = this.buttonHelpFixSquares;
        var scene = this;

        buttonValidate.disable();
        buttonCancel.disable();

        gameConfig.useHelpFixSquares(s, function(squares, count){
            buttonValidate.enable();
            buttonCancel.enable();

            if (count >= 0) {
                buttonHelp.updateCounterText(count);
            }

            if (_.isUndefined(squares)) {
                return;
            }

            if (squares.length == 0) {
                scene.helpTextBackground.setVisible(false);
                scene.helpTextBackgroundOk.setVisible(true);
                scene.updateHelpText(gameConfig.help.fixSquares.noerror);
                buttonValidate.disable();
            } else {
                var currentCellMode = gameConfig.cellMode;
                gameConfig.cellMode = config.cellMode.reveal;
                for (var i = 0; i < squares.length; i++) {
                    var square = squares[i];
                    scene.updateCell(square.column, square.row, (square.value === "1" ? config.state.selected : config.state.unselected), true);
                    scene.updateColHintTextColor(square.column, false);
                    scene.updateRowHintTextColor(square.row, false);
                }
                gameConfig.cellMode = currentCellMode;

                scene.helpTextBackground.setVisible(true);
                scene.helpTextBackgroundOk.setVisible(false);
            }
        });
    }

    useHelpRevealRowOrColumn() {
        if (_.isUndefined(gameConfig.useHelpRevealRowOrColumn) || _.isUndefined(gameConfig.helpSelectedRowOrColIndex)) {
            return;
        }

        var buttonValidate = this.buttonHelpModeValidate;
        var buttonCancel = this.buttonHelpModeCancel;
        var buttonHelp = this.buttonHelpRevealRowOrCol;
        var scene = this;

        buttonValidate.disable();
        buttonCancel.disable();

        gameConfig.useHelpRevealRowOrColumn(gameConfig.helpSelectedRowOrColIsRow, gameConfig.helpSelectedRowOrColIndex, function(squares, count){
            buttonValidate.enable();
            buttonCancel.enable();

            if (count >= 0) {
                buttonHelp.updateCounterText(count);
            }

            if (_.isUndefined(squares)) {
                return;
            }

            var currentCellMode = gameConfig.cellMode;
            gameConfig.cellMode = config.cellMode.reveal;
            for (var i = 0; i < squares.length; i++) {
                var square = squares[i];
                scene.updateCell(square.column, square.row, (square.value === "1" ? config.state.selected : config.state.unselected), true);
                scene.updateColHintTextColor(square.column, false);
                scene.updateRowHintTextColor(square.row, false);
            }
            gameConfig.cellMode = currentCellMode;

            gameConfig.helpSelectedRowOrColIndex = undefined;
            gameConfig.helpSelectedRowOrColIsRow = undefined;
        });
    }

    useHelpRevealSquare() {
        if (_.isUndefined(gameConfig.useHelpRevealSquare) || _.isUndefined(gameConfig.helpSelectedSquare)) {
            return;
        }

        var buttonValidate = this.buttonHelpModeValidate;
        var buttonCancel = this.buttonHelpModeCancel;
        var buttonHelp = this.buttonHelpRevealSquare;
        var scene = this;

        buttonValidate.disable();
        buttonCancel.disable();

        gameConfig.useHelpRevealSquare(gameConfig.helpSelectedSquare.x, gameConfig.helpSelectedSquare.y, function(squares, count){
            buttonValidate.enable();
            buttonCancel.enable();

            scene.clearHelpSelectedSquare();

            if (count >= 0) {
                buttonHelp.updateCounterText(count);
            }

            if (_.isUndefined(squares)) {
                return;
            }

            var currentCellMode = gameConfig.cellMode;
            gameConfig.cellMode = config.cellMode.reveal;
            for (var i = 0; i < squares.length; i++) {
                var square = squares[i];
                scene.updateCell(square.column, square.row, (square.value === "1" ? config.state.selected : config.state.unselected), true);
                scene.updateColHintTextColor(square.column, false);
                scene.updateRowHintTextColor(square.row, false);
            }
            gameConfig.cellMode = currentCellMode;

            gameConfig.helpSelectedSquare = undefined;
        });
    }

    helpSelectRowOrColumn(index, isRow) {
        if (!this.helpModeEnabled || !this.helpType == config.help.mode.revealRowOrCol) {
            return;
        }

        this.clearHelpSelectedRowOrColumn();

        gameConfig.helpSelectedRowOrColIsRow = isRow;
        gameConfig.helpSelectedRowOrColIndex = index;

        var currentCellMode = gameConfig.cellMode;
        gameConfig.cellMode = config.cellMode.help;
        
        var count = (gameConfig.helpSelectedRowOrColIsRow ? gameConfig.cols.count : gameConfig.rows.count);
        for (var i = 0; i < count; i++) {
            var x = (gameConfig.helpSelectedRowOrColIsRow ? i : gameConfig.helpSelectedRowOrColIndex);
            var y = (gameConfig.helpSelectedRowOrColIsRow ? gameConfig.helpSelectedRowOrColIndex : i);
            this.updateCell(x, y, config.state.selected, false, false);
        }

        gameConfig.cellMode = currentCellMode;
    }

    // Helper functions.
    getXYFromEventPosition(e) {
        var gridTL = this.grid.background.getTopLeft();

        var eventPosition = e.position;
        var pointerX = eventPosition.x - gridTL.x * this.grid.container.scaleX - this.grid.container.x;
        var pointerY = eventPosition.y - gridTL.y * this.grid.container.scaleY - this.grid.container.y;

        var squareSize  = (config.grid.cell.size + config.grid.line.width.thin);
        var sectionSize = ((squareSize * config.grid.cell.groupSize) + (config.grid.line.width.bold) - config.grid.line.width.thin) * this.grid.container.scaleX;

        var countOfSectionX = Math.floor(pointerX/sectionSize);
        var countOfSectionY = Math.floor(pointerY/sectionSize);

        var x = Math.floor((pointerX - (countOfSectionX * sectionSize))/(squareSize * this.grid.container.scaleX)) + (countOfSectionX * config.grid.cell.groupSize);
        var y = Math.floor((pointerY - (countOfSectionY * sectionSize))/(squareSize * this.grid.container.scaleY)) + (countOfSectionY * config.grid.cell.groupSize);

        return {x: x, y: y}
    }

    gridCoordinatesToSolutionIndex(x, y) {
        return y * gameConfig.cols.count + x;
    }

    stateOfCell(x, y) {
        var index = this.gridCoordinatesToSolutionIndex(x, y);
        return gameConfig.solution.charAt(index);
    }

    cellAt(x, y) {
        return this.grid.cells[y][x];
    }

    encodeSavedSolution() {
        return gameConfig.solution;
    }

    encodeSolution() {
        var encoded = '';

        var current = false;
        var count = 0;
        for (var i=0; i<gameConfig.solution.length; i++) {
            var s = gameConfig.solution.charAt(i);
            var isFilled = (s == config.state.selected || s == config.state.selectedTemp);
            if (isFilled == current) {
                count++;
            } else {
                if (count > 0) {
                    encoded += (count * (current ? 1 : -1)).toString()+';';
                }
                current = isFilled;
                count = 1;
            }
        }

        if (count > 0) {
            encoded += (count * (current ? 1 : -1)).toString()+';';
        }

        return encoded;
    }

    decodeSolution() {
        var parts = gameConfig.solution.split(';', -1);
        if (parts.length == 1) {
            return gameConfig.solution;
        }

        var decoded = '';
        for (var i=0; i<parts.length; i++) {
            var count = parseInt(parts[i], 10);
            var char = (count > 0 ? '1' : '0');
            count = Math.abs(count);
            for (var j=0; j<count; j++) {
                decoded += char;
            }
        }

        return decoded;
    }

    createHintText() {
        var text = this.add.text(0, 0, '', {font: config.grid.hints.font, fill: config.grid.hints.color});
        text.setFontStyle(config.grid.hints.style);
        text.setVisible(false);
        text.setName('n'); // 'n' is for normal, 'd' is for disabled.
        this.grid.container.add(text);
        return text;
    }

    dequeueHintText() {
        if (this.unusedHints.length == 0) {
            return this.createHintText();
        }

        return this.unusedHints.pop();
    }

    enqueueHintText(text) {
        text.setPosition(0, 0);
        text.setText('');
        text.setColor(config.grid.hints.color);
        text.setVisible(false);
        text.setName('n'); // 'n' is for normal, 'd' is for disabled.
        this.unusedHints.push(text);
    }

    colorHintText(text, highlighted = false) {
        if (highlighted) {
            text.setColor((text.name == 'd' && config.grid.hints.disabledEnabled ? config.grid.hints.highlightedAndDisabledColor : config.grid.hints.highlightedColor));
        } else {
            text.setColor((text.name == 'd' && config.grid.hints.disabledEnabled ? config.grid.hints.disabledColor : config.grid.hints.color));
        }
    }

    updateColHint(i) {
        gameConfig.cols.hints[i] = this.buildColHints(i);
    }

    updateRowHint(i) {
        gameConfig.rows.hints[i] = this.buildRowHints(i);
    }

    buildColHints(i) {
        var s = '';
        for (var y=0; y<gameConfig.rows.count; y++) {
            s += gameConfig.solution.substr(y * gameConfig.cols.count + i, 1);
        }

        return this.buildHints(s);
    }

    buildRowHints(i) {
        var s = gameConfig.solution.substr(i * gameConfig.cols.count, gameConfig.cols.count);
        return this.buildHints(s);
    }

    buildHints(s) {
        var hints = [];

        var count = 0;
        for (var i=0; i<s.length; i++) {
            var char = s.charAt(i);
            if ((char == config.state.unknown || char == config.state.unselected || char == config.state.unselectedTemp) && count > 0) {
                hints.push(count);
                count = 0;
            } else if (char == config.state.selected || char == config.state.selectedTemp) {
                count++;
            }
        }
        if (hints.length == 0 || count > 0) {
            hints.push(count);
        }

        return hints.join(',');
    }

    clearHelpSelectedSquare() {
        if (_.isUndefined(gameConfig.helpSelectedSquare)) {
            return;
        }

        var currentCellMode = gameConfig.cellMode;
        gameConfig.cellMode = config.cellMode.normal;
        
        this.updateCell(gameConfig.helpSelectedSquare.x, gameConfig.helpSelectedSquare.y, this.stateOfCell(gameConfig.helpSelectedSquare.x, gameConfig.helpSelectedSquare.y), false, false);
        gameConfig.helpSelectedSquare = undefined;

        gameConfig.cellMode = currentCellMode;
    }

    clearHelpSelectedRowOrColumn() {
        if (_.isUndefined(gameConfig.helpSelectedRowOrColIndex)) {
            return;
        }

        var currentCellMode = gameConfig.cellMode;
        gameConfig.cellMode = config.cellMode.normal;
        
        var count = (gameConfig.helpSelectedRowOrColIsRow ? gameConfig.cols.count : gameConfig.rows.count);
        for (var i = 0; i < count; i++) {
            var x = (gameConfig.helpSelectedRowOrColIsRow ? i : gameConfig.helpSelectedRowOrColIndex);
            var y = (gameConfig.helpSelectedRowOrColIsRow ? gameConfig.helpSelectedRowOrColIndex : i);
            this.updateCell(x, y, this.stateOfCell(x, y), false, false);
        }

        gameConfig.helpSelectedRowOrColIndex = undefined;
        gameConfig.helpSelectedRowOrColIsRow = undefined;

        gameConfig.cellMode = currentCellMode;
    }

    // UI update functions.
    updateCell(x, y, state, updateSolution = true, updatePreview = true) {
        if (updateSolution) {
            var stateIndex = this.gridCoordinatesToSolutionIndex(x, y);
            gameConfig.solution = gameConfig.solution.substr(0, stateIndex) + state + gameConfig.solution.substr(stateIndex+1);
        }

        var cell = this.cellAt(x, y);

        switch(state) {
            case config.state.selected:
            cell.setFrame((gameConfig.cellMode == config.cellMode.reveal ? config.frame.cell.selectedRevealed : (gameConfig.cellMode == config.cellMode.help ? config.frame.cell.help : config.frame.cell.selected)));
            break;
            case config.state.selectedTemp:
            cell.setFrame(config.frame.cell.selectedTemp);
            break;
            case config.state.unselected:
            cell.setFrame(gameConfig.cellMode == config.cellMode.reveal ? config.frame.cell.unselectedRevealed : config.frame.cell.unselected);
            break;
            case config.state.unselectedTemp:
            cell.setFrame(config.frame.cell.unselectedTemp);
            break;
            default:
            break;
        }
        
        cell.setVisible(state != config.state.unknown);

        if (cell.visible) {
            this.updateCellBorder(cell);
        }

        if (updatePreview) {
            this.updatePreviewCell(x, y, state);
        }
    }

    updateCellBorder(cell) {
        cell.setScale(config.grid.cell.bordered ? 0.9 : 1.0);
    }

    updatePreviewCell(x, y, state) {
        if (!config.preview.enabled) {
            return;
        }

        var previewCellName = x.toString() + '.' + y.toString();
        var previewCell = this.grid.preview.container.getByName(previewCellName);

        if ((state == config.state.unknown || state == config.state.unselected || state == config.state.unselectedTemp) && previewCell !== null) {
            this.grid.preview.container.remove(previewCell, true);
        } else if ((state == config.state.selected || state == config.state.selectedTemp) && previewCell === null) {
            var previewTL = this.grid.preview.background.getTopLeft();        
            previewCell = this.add.rectangle(
                (previewTL.x + x*config.preview.cell.size + gameConfig.cols.count), (previewTL.y + y*config.preview.cell.size + gameConfig.rows.count),
                config.preview.cell.size, config.preview.cell.size,
                config.preview.cell.color, 1
            );
            previewCell.setOrigin(0, 0);
            previewCell.setName(previewCellName);
            this.grid.preview.container.add(previewCell);
        }
    }

    updateHandles() {
        if (!gameConfig.createMode) {
            return;
        }

        var gridTL = this.grid.background.getTopLeft();
        var gridBR = this.grid.background.getBottomRight();

        var handleHorizontalY = gridTL.y + (gridBR.y - gridTL.y)/2.0;
        var handleVerticalX = gridTL.x + (gridBR.x - gridTL.x)/2.0;

        this.grid.handles.left.setPosition(gridTL.x - config.grid.extend.handle.margin, handleHorizontalY);
        this.grid.handles.right.setPosition(gridBR.x + config.grid.extend.handle.margin, handleHorizontalY);
        this.grid.handles.bottom.setPosition(handleVerticalX, gridBR.y + config.grid.extend.handle.margin);
        this.grid.handles.top.setPosition(handleVerticalX, gridTL.y - config.grid.extend.handle.margin);

        this.grid.rowHintsContainer.setPosition(gridTL.x - config.grid.extend.hints.margin, 0);
        this.grid.colHintsContainer.setPosition(0, gridTL.y - config.grid.extend.hints.margin);
    }

    updateHandleLabel(position) {
        switch (position) {
            case 'l':
                this.extendGrid.label.setText((gameConfig.cols.count + this.extendGrid.count).toString() + ' x ' + gameConfig.rows.count.toString());
                this.extendGrid.label.setPosition(this.grid.handles.left.x, this.grid.handles.left.y - 35);
                break;

            case 'r':
                this.extendGrid.label.setText((gameConfig.cols.count + this.extendGrid.count).toString() + ' x ' + gameConfig.rows.count.toString());
                this.extendGrid.label.setPosition(this.grid.handles.right.x, this.grid.handles.right.y - 35);
                break;

            case 't':
                this.extendGrid.label.setText(gameConfig.cols.count.toString() + ' x ' + (gameConfig.rows.count + this.extendGrid.count).toString());
                this.extendGrid.label.setPosition(this.grid.handles.top.x - 40, this.grid.handles.top.y);
                break;

            case 'b':
                this.extendGrid.label.setText(gameConfig.cols.count.toString() + ' x ' + (gameConfig.rows.count + this.extendGrid.count).toString());
                this.extendGrid.label.setPosition(this.grid.handles.bottom.x - 40, this.grid.handles.bottom.y);
                break;
        
            default:
                break;
        }
    }

    updateHistoryButtons() {
        if (_.isUndefined(this.buttonHistoryBack) || _.isUndefined(this.buttonHistoryForward)) {
            return;
        }

        if (this.history.hasBackwardEntries()) {
            this.buttonHistoryBack.enable();
        } else {
            this.buttonHistoryBack.disable();
        }

        if (this.history.hasForwardEntries()) {
            this.buttonHistoryForward.enable();
        } else {
            this.buttonHistoryForward.disable();
        }
    }

    updateZoomButtons() {
        if (this.buttonZoomOut === undefined || this.buttonZoomIn === undefined) {
            return;
        }

        var zoom = this.grid.container.scaleX;

        if (zoom <= config.grid.zoom.minimum) {
            this.buttonZoomOut.disable();
        } else {
            this.buttonZoomOut.enable();
        }

        if (zoom >= config.grid.zoom.maximum) {
            this.buttonZoomIn.disable();
        } else {
            this.buttonZoomIn.enable();
        }
    }

    initGrid() {
        var scene = this;
        var gridElements = [];

        // The grid.
        this.grid.border = this.add.rectangle(0, 0, 0, 0, config.grid.border.color, 1);
        this.grid.border.setOrigin(0, 0);
        gridElements.push(this.grid.border);

        this.grid.background = this.add.rectangle(1, 1, 0, 0, config.grid.background.color, 1);
        this.grid.background.setOrigin(0, 0);
        gridElements.push(this.grid.background);

        var gridTL = this.grid.background.getTopLeft();

        // The alternate backgrounds.
        for (var x=0; x<config.grid.size.maximum.cols; x+=config.grid.cell.groupSize) {
            for (var y=0; y<config.grid.size.maximum.rows; y+=config.grid.cell.groupSize) {
                var indexX = x/config.grid.cell.groupSize;
                var indexY = y/config.grid.cell.groupSize;
                if (indexX%2 == indexY%2) {
                    var rect = this.add.rectangle(
                        gridTL.x + (this.altBGSize*indexX) + (config.grid.line.width.thin*indexX) + ((config.grid.line.width.bold-config.grid.line.width.thin)*indexX),
                        gridTL.y + (this.altBGSize*indexY) + (config.grid.line.width.thin*indexY) + ((config.grid.line.width.bold-config.grid.line.width.thin)*indexY),
                        this.altBGSize, this.altBGSize,
                        config.grid.background.alternateColor[config.grid.background.contrastLevel], 1
                    );
                    rect.setOrigin(0, 0);
                    rect.setVisible(false);
                    this.grid.alternateBackgrounds.push(rect);
                    gridElements.push(rect);
                }
            }
        }

        // The lines.
        for (var i=0; i<config.grid.size.maximum.cols-1; i++) {
            var line = this.add.line(0, 0, 0, 0, 0, 0, config.grid.line.color.thin, 1);
            if (i>0 && (i+1)%config.grid.cell.groupSize == 0) {
                line.setLineWidth(config.grid.line.width.bold/2);
                line.setStrokeStyle(config.grid.line.width.bold/2, config.grid.line.color.bold);
            } else {
                line.setLineWidth(config.grid.line.width.thin);
                line.setStrokeStyle(config.grid.line.width.thin, config.grid.line.color.thin);
            }
            line.setOrigin(0,0);
            line.setVisible(false);

            this.grid.lines.cols.push(line);
            gridElements.push(line);
        }
        for (var i=0; i<config.grid.size.maximum.rows-1; i++) {
            var line = this.add.line(0, 0, 0, 0, 0, 0, config.grid.line.color.thin, 1);
            if (i>0 && (i+1)%config.grid.cell.groupSize == 0) {
                line.setLineWidth(config.grid.line.width.bold/2);
                line.setStrokeStyle(config.grid.line.width.bold/2, config.grid.line.color.bold);
            } else {
                line.setLineWidth(config.grid.line.width.thin);
                line.setStrokeStyle(config.grid.line.width.thin, config.grid.line.color.thin);
            }
            line.setOrigin(0,0);
            line.setVisible(false);

            this.grid.lines.rows.push(line);
            gridElements.push(line);
        }

        // The cells.
        var decalY = 0;
        for (var y=0; y<config.grid.size.maximum.rows; y++) {
            if (y>=config.grid.cell.groupSize && y%config.grid.cell.groupSize==0) {
                decalY += (config.grid.line.width.bold - config.grid.line.width.thin);
            }

            var row = [], decalX = 0;
            for (var x=0; x<config.grid.size.maximum.cols; x++) {
                if (x>=config.grid.cell.groupSize && x%config.grid.cell.groupSize==0) {
                    decalX += (config.grid.line.width.bold - config.grid.line.width.thin);
                }

                var cell = this.add.sprite(gridTL.x + x*this.cellSize + decalX + this.cellSize/2, gridTL.y + y*this.cellSize + decalY + this.cellSize/2, 'cells');
                this.updateCellBorder(cell);
                cell.setVisible(false);
                row.push(cell);
                gridElements.push(cell);
            }
            this.grid.cells.push(row);
        }

        // The preview.
        if (config.preview.enabled) {
            this.grid.preview.border = this.add.rectangle(0, 0, 0, 0, config.preview.border.color, 1);
            this.grid.preview.background = this.add.rectangle(1, 1, 0, 0, config.preview.background.color, 1);
            this.grid.preview.container = this.add.container(0, 0, [this.grid.preview.border, this.grid.preview.background]);
            this.grid.preview.container.setScale(config.preview.scale);

            gridElements.push(this.grid.preview.container);
        }

        // The handles.
        if (gameConfig.createMode) {
            this.extendGrid.rect = this.add.rectangle(0, 0, 0, 0, config.grid.extend.moreColor);
            this.extendGrid.rect.setAlpha(0.7);
            this.extendGrid.rect.setOrigin(0, 0);
            this.extendGrid.rect.setVisible(false);
            gridElements.push(this.extendGrid.rect);

            this.extendGrid.label = this.add.text(0, 0, '', {font: config.grid.extend.label.font.size + 'px ' + config.grid.extend.label.font.family, fill: config.grid.extend.label.color});
            this.extendGrid.label.setVisible(false);
            this.extendGrid.label.setFontStyle(config.grid.extend.label.font.style);
            gridElements.push(this.extendGrid.label);

            this.grid.handles.right = this.add.image(0, 0, 'handle_h');
            this.grid.handles.right.setName('rHandle');
            this.grid.handles.right.setScale(0.5);
            this.grid.handles.right.setInteractive({ cursor: 'col-resize' });
            this.grid.handles.right.on('pointerdown', function(e) {
                scene.extendGrid.direction = 'r';
                scene.extendGrid.startDragging = e.event.layerX;
                scene.extendGrid.count = 0;
                scene.extendGrid.rect.setPosition(scene.grid.border.getTopRight().x, scene.grid.border.getTopRight().y);
                scene.extendGrid.rect.setSize(0, scene.grid.border.height);
                scene.extendGrid.rect.setVisible(true);
                scene.extendGrid.label.setOrigin(0.5, 1);
                scene.updateHandleLabel('r');
                scene.extendGrid.label.setVisible(true);
            });
            this.input.setDraggable(this.grid.handles.right);
            gridElements.push(this.grid.handles.right);

            this.grid.handles.left = this.add.image(0, 0, 'handle_h');
            this.grid.handles.left.setName('lHandle');
            this.grid.handles.left.setAngle(180);
            this.grid.handles.left.setScale(0.5);
            this.grid.handles.left.setInteractive({ cursor: 'col-resize' });
            this.grid.handles.left.on('pointerdown', function(e) {
                scene.extendGrid.direction = 'l';
                scene.extendGrid.startDragging = e.event.layerX;
                scene.extendGrid.count = 0;
                scene.extendGrid.rect.setPosition(scene.grid.border.getTopLeft().x, scene.grid.border.getTopLeft().y);
                scene.extendGrid.rect.setSize(0, scene.grid.border.height);
                scene.extendGrid.rect.setVisible(true);
                scene.extendGrid.label.setOrigin(0.5, 1);
                scene.updateHandleLabel('l');
                scene.extendGrid.label.setVisible(true);
            });
            this.input.setDraggable(this.grid.handles.left);
            gridElements.push(this.grid.handles.left);

            this.grid.handles.bottom = this.add.image(0, 0, 'handle_v');
            this.grid.handles.bottom.setName('bHandle');
            this.grid.handles.bottom.setAngle(90);
            this.grid.handles.bottom.setScale(0.5);
            this.grid.handles.bottom.setInteractive({ cursor: 'row-resize' });
            this.grid.handles.bottom.on('pointerdown', function(e) {
                scene.extendGrid.direction = 'b';
                scene.extendGrid.startDragging = e.event.layerY;
                scene.extendGrid.count = 0;
                scene.extendGrid.rect.setPosition(scene.grid.border.getBottomLeft().x, scene.grid.border.getBottomLeft().y);
                scene.extendGrid.rect.setSize(scene.grid.border.width, 0);
                scene.extendGrid.rect.setVisible(true);
                scene.extendGrid.label.setOrigin(1, 0.5);
                scene.updateHandleLabel('b');
                scene.extendGrid.label.setVisible(true);
            });
            this.input.setDraggable(this.grid.handles.bottom);
            gridElements.push(this.grid.handles.bottom);

            this.grid.handles.top = this.add.image(0, 0, 'handle_v');
            this.grid.handles.top.setName('tHandle');
            this.grid.handles.top.setAngle(-90);
            this.grid.handles.top.setScale(0.5);
            this.grid.handles.top.setInteractive({ cursor: 'row-resize' });
            this.grid.handles.top.on('pointerdown', function(e) {
                scene.extendGrid.direction = 't';
                scene.extendGrid.startDragging = e.event.layerY;
                scene.extendGrid.count = 0;
                scene.extendGrid.rect.setPosition(scene.grid.border.getTopLeft().x, scene.grid.border.getTopLeft().y);
                scene.extendGrid.rect.setSize(scene.grid.border.width, 0);
                scene.extendGrid.rect.setVisible(true);
                scene.extendGrid.label.setOrigin(1, 0.5);
                scene.updateHandleLabel('t');
                scene.extendGrid.label.setVisible(true);
            });
            this.input.setDraggable(this.grid.handles.top);
            gridElements.push(this.grid.handles.top);
        }

        // The highlighted background hints.
        this.grid.highlightedBackgroundHintRow = this.add.rectangle(0, 0, 0, this.cellSize, config.grid.hints.highlightedBackgroundColor, config.grid.hints.highlightedBackgroundAlpha);
        this.grid.highlightedBackgroundHintRow.setVisible(false);
        gridElements.push(this.grid.highlightedBackgroundHintRow);

        this.grid.highlightedBackgroundHintCol = this.add.rectangle(0, 0, this.cellSize, 0, config.grid.hints.highlightedBackgroundColor, config.grid.hints.highlightedBackgroundAlpha);
        this.grid.highlightedBackgroundHintCol.setVisible(false);
        gridElements.push(this.grid.highlightedBackgroundHintCol);

        // The hints containers.
        this.grid.rowHintsContainer = this.add.container(0, 0);
        this.grid.rowHintsContainer.setName('rowHintsContainer');
        gridElements.push(this.grid.rowHintsContainer);

        this.grid.colHintsContainer = this.add.container(0, 0);
        this.grid.colHintsContainer.setName('colHintsContainer');
        gridElements.push(this.grid.colHintsContainer);

        // The grid container.
        this.grid.container = this.add.container(0, 0, gridElements);
        this.grid.container.setName('gridContainer');
        this.grid.container.setSize(this.cellSize * config.grid.size.maximum.cols * 4, this.cellSize * config.grid.size.maximum.rows * 4);
        this.grid.container.setInteractive({ cursor: 'move' });

        this.updateDraggableGrid();

        // The buttons.
        this.menuContainer = this.add.container(0, config.scene.size.margin);
        this.buttonsContainer = this.add.container(0, config.scene.size.margin);
        this.menuContainer.add(this.buttonsContainer);

        var buttonPositionX = (config.buttons.size.width/2);
        var buttonPositionY = (config.buttons.size.height/2);
        var space = (config.buttons.size.height + 5);
        var nextButton = function() {
            buttonPositionY = buttonPositionY + space;
            scene.menuContainer.setData('height', buttonPositionY);
        }
        var spacer = function() {
            buttonPositionY = buttonPositionY + space/2;
            scene.menuContainer.setData('height', buttonPositionY);
        }

        this.buttonBack = new BButton(this, 0, 0, null, window.gameConfig.text.back, '', '');
        this.buttonBack.setScale(0.5);
        this.buttonBack.setX(this.buttonBack.width/2 * this.buttonBack.scaleX + config.scene.size.margin);
        this.buttonBack.setY(this.buttonBack.height/2 * this.buttonBack.scaleY + config.scene.size.margin);
        this.buttonBack.setAction(function(e) {
            hideGameUI();
        });

        if (!gameConfig.createMode) {
            // The timer.
            this.timer = this.add.text(buttonPositionX, buttonPositionY, '00:00', config.timer.textStyle);
            this.timer.setOrigin(0.5, 0.5);

            this.menuContainer.add(this.timer);
        }

        nextButton();
        spacer();

        this.buttonToggleMenu = new BButton(this, 0, 0, null, '', 'buttons', config.buttons.frames.plaintoggle);
        this.buttonToggleMenu.setScale(0.5);
        this.buttonToggleMenu.setAngle(180);
        this.buttonToggleMenu.setX(buttonPositionX + config.buttons.size.width/2 - this.buttonToggleMenu.width/2*this.buttonToggleMenu.scaleY);
        this.buttonToggleMenu.setY(config.scene.size.margin + this.buttonToggleMenu.height/2*this.buttonToggleMenu.scaleY);
        this.buttonToggleMenu.setAction(function(e) {
            scene.buttonsContainer.setVisible(!scene.buttonsContainer.visible);
            scene.buttonToggleMenu.setAngle(scene.buttonsContainer.visible ? 180 : 0);
        });
        this.menuContainer.add(this.buttonToggleMenu);

        this.buttonGamePause = new BButton(this, 0, buttonPositionY, null, '', 'buttons', config.buttons.frames.pause);
        this.buttonGamePause.setX(buttonPositionX - config.buttons.size.width/2 + this.buttonGamePause.width/2);
        this.buttonGamePause.setAction(function(e) {
            hideGameUI();
        });

        this.buttonZoomOut = new BButton(this, 0, buttonPositionY, null, '', 'buttons', config.buttons.frames.zoomOut);
        this.buttonZoomOut.setX(buttonPositionX - this.buttonZoomOut.width/2);
        this.buttonZoomOut.setAction(function(e) {
            scene.zoomOut(e);
        });

        this.buttonZoomIn = new BButton(this, 0, buttonPositionY, null, '', 'buttons', config.buttons.frames.zoomIn);
        this.buttonZoomIn.setX(buttonPositionX + this.buttonZoomIn.width/2);
        this.buttonZoomIn.setAction(function(e) {
            scene.zoomIn(e);
        });

        this.buttonSettings = new BButton(this, 0, buttonPositionY, null, '', 'buttons', config.buttons.frames.settings);
        this.buttonSettings.setX(buttonPositionX + config.buttons.size.width/2 - this.buttonGamePause.width/2);
        this.buttonSettings.enable();
        this.buttonSettings.setAction(function(e) {
            scene.showSettings(e);
        });

        this.buttonGamePause.setScale(0.85);
        this.buttonZoomIn.setScale(0.85);
        this.buttonZoomOut.setScale(0.85);
        this.buttonSettings.setScale(0.85);

        nextButton();
        spacer();

        this.buttonsContainer.add(this.buttonZoomIn);
        this.buttonsContainer.add(this.buttonZoomOut);
        this.buttonsContainer.add(this.buttonSettings);
        this.buttonsContainer.add(this.buttonGamePause);

        if (gameConfig.createMode) {
            this.buttonSave = new BButton(this, buttonPositionX, buttonPositionY, null, window.gameConfig.text.save, 'buttons', config.buttons.frames.save);
            this.buttonSave.setAction(function(e) {
                scene.saveCreation(e);
            });

            nextButton();
            spacer();

            this.buttonCheckLogical = new BButton(this, buttonPositionX, buttonPositionY, null, window.gameConfig.text.logical, 'buttons', config.buttons.frames.logical);
            this.buttonCheckLogical.setAction(function(e) {
                scene.checkLogical(e);
            });

            nextButton();

            this.buttonInvertCells = new BButton(this, buttonPositionX, buttonPositionY, null, window.gameConfig.text.invert, 'buttons', config.buttons.frames.invert);
            this.buttonInvertCells.setAction(function(e) {
                scene.invertCells(e);
            });

            nextButton();

            this.buttonImageToPuzzle = new BButton(this, buttonPositionX, buttonPositionY, null, window.gameConfig.text.image, 'buttons', config.buttons.frames.image);
            this.buttonImageToPuzzle.setAction(function(e) {
                scene.createFromImage(e);
            });

            nextButton();
            spacer();

            this.buttonReset = new BButton(this, buttonPositionX, buttonPositionY, null, window.gameConfig.text.reset, 'buttons', config.buttons.frames.reset);
            this.buttonReset.setAction(function(e) {
                scene.enableHelpMode(config.help.mode.reset, true);
            });

            nextButton();

            this.buttonsContainer.add(this.buttonSave);
            this.buttonsContainer.add(this.buttonCheckLogical);
            this.buttonsContainer.add(this.buttonInvertCells);
            this.buttonsContainer.add(this.buttonImageToPuzzle);
            this.buttonsContainer.add(this.buttonReset);
        } else {
            this.buttonCheck = new BButton(this, buttonPositionX, buttonPositionY, null, window.gameConfig.text.check, 'buttons', config.buttons.frames.check);
            this.buttonCheck.setAction(function(e) {
                scene.checkGame(e);
            });

            nextButton();
            spacer();

            this.buttonSave = new BButton(this, buttonPositionX, buttonPositionY, null, window.gameConfig.text.save, 'buttons', config.buttons.frames.save, !gameConfig.testMode);
            if (isTestMode()) {
                this.buttonSave.setAction(function(e) {
                    scene.saveGame(e);
                });
            } else {
                this.buttonSave.setAction(function(e) {
                    scene.enableHelpMode(config.help.mode.save, true);
                });
            }

            nextButton();
            spacer();

            this.buttonCellSelected = new BButton(this, 0, buttonPositionY, null, '', 'buttons', config.buttons.frames.cellSelected);
            this.buttonCellSelected.setX(buttonPositionX - config.buttons.size.width/2 + this.buttonCellSelected.width/2);
            this.buttonCellSelected.setAction(function(e) {
                scene.updateSelectedState(config.state.selected);
            });

            this.buttonCellUnselected = new BButton(this, 0, buttonPositionY, null, '', 'buttons', config.buttons.frames.cellUnselected);
            this.buttonCellUnselected.setX(buttonPositionX - config.buttons.size.width/6 + this.buttonCellUnselected.width/6);
            this.buttonCellUnselected.setAction(function(e) {
                scene.updateSelectedState(config.state.unselected);
            });

            this.buttonCellSelectedTemp = new BButton(this, 0, buttonPositionY, null, '', 'buttons', config.buttons.frames.cellSelectedTemp);
            this.buttonCellSelectedTemp.setX(buttonPositionX + config.buttons.size.width/6 - this.buttonCellSelectedTemp.width/6);
            this.buttonCellSelectedTemp.setAction(function(e) {
                scene.updateSelectedState(config.state.selectedTemp);
            });

            this.buttonCellUnselectedTemp = new BButton(this, 0, buttonPositionY, null, '', 'buttons', config.buttons.frames.cellUnselectedTemp);
            this.buttonCellUnselectedTemp.setX(buttonPositionX + config.buttons.size.width/2 - this.buttonCellUnselectedTemp.width/2);
            this.buttonCellUnselectedTemp.setAction(function(e) {
                scene.updateSelectedState(config.state.unselectedTemp);
            });

            nextButton();

            this.buttonValidateTemp = new BButton(this, buttonPositionX, buttonPositionY, null, window.gameConfig.text.tempValidate, 'buttons', config.buttons.frames.validateTempCells);
            this.buttonValidateTemp.setAction(function(e) {
                scene.validateTempCells(e);
            });

            nextButton();

            this.buttonDeleteTemp = new BButton(this, buttonPositionX, buttonPositionY, null, window.gameConfig.text.tempDelete, 'buttons', config.buttons.frames.deleteTempCells);
            this.buttonDeleteTemp.setAction(function(e) {
                scene.deleteTempCells(e);
            });

            nextButton();
            spacer();

            if (!isTestMode()) {
                // Help buttons.
                this.buttonHelpRevealSquare = new BButton(this, buttonPositionX, buttonPositionY, null, window.gameConfig.text.helpRevealSquare, 'buttons', config.buttons.frames.helpRevealSquare, true, true);
                this.buttonHelpRevealSquare.setAction(function(e) {
                    scene.enableHelpMode(config.help.mode.revealSquare, true);
                });

                nextButton();
    
                this.buttonHelpRevealRowOrCol = new BButton(this, buttonPositionX, buttonPositionY, null, window.gameConfig.text.helpRevealRowOrCol, 'buttons', config.buttons.frames.helpRevealRowOrCol, true, true);
                this.buttonHelpRevealRowOrCol.setAction(function(e) {
                    scene.enableHelpMode(config.help.mode.revealRowOrCol, true);
                });

                nextButton();
    
                this.buttonHelpFixSquares = new BButton(this, buttonPositionX, buttonPositionY, null, window.gameConfig.text.helpFixSquares, 'buttons', config.buttons.frames.helpFixSquares, true, true);
                this.buttonHelpFixSquares.setAction(function(e) {
                    scene.enableHelpMode(config.help.mode.fixSquares, true);
                });

                nextButton();
                spacer();

                this.buttonsContainer.add(this.buttonHelpRevealSquare);
                this.buttonsContainer.add(this.buttonHelpRevealRowOrCol);
                this.buttonsContainer.add(this.buttonHelpFixSquares);

                this.updateGameHelpRevealSquareCounter(gameConfig.help.revealSquare.count);
                this.updateGameHelpRevealRowOrColCounter(gameConfig.help.revealRowOrColumn.count);
                this.updateGameHelpFixSquaresCounter(gameConfig.help.fixSquares.count);
            }

            this.buttonHistoryBack = new BButton(this, buttonPositionX, buttonPositionY, null, window.gameConfig.text.historyBack, 'buttons', config.buttons.frames.historyBack);
            this.buttonHistoryBack.setAction(function(e) {
                scene.historyBack(e);
            });

            nextButton();

            this.buttonHistoryForward = new BButton(this, buttonPositionX, buttonPositionY, null, window.gameConfig.text.historyForward, 'buttons', config.buttons.frames.historyForward);
            this.buttonHistoryForward.setAction(function(e) {
                scene.historyForward(e);
            });

            nextButton();
            spacer();

            this.buttonReset = new BButton(this, buttonPositionX, buttonPositionY, null, window.gameConfig.text.reset, 'buttons', config.buttons.frames.reset);
            this.buttonReset.setAction(function(e) {
                scene.enableHelpMode(config.help.mode.reset, true);
            });

            nextButton();

            this.buttonsContainer.add(this.buttonCheck);
            this.buttonsContainer.add(this.buttonSave);
            this.buttonsContainer.add(this.buttonReset);
            this.buttonsContainer.add(this.buttonHistoryBack);
            this.buttonsContainer.add(this.buttonHistoryForward);
            this.buttonsContainer.add(this.buttonCellSelected);
            this.buttonsContainer.add(this.buttonCellUnselected);
            this.buttonsContainer.add(this.buttonCellSelectedTemp);
            this.buttonsContainer.add(this.buttonCellUnselectedTemp);
            this.buttonsContainer.add(this.buttonValidateTemp);
            this.buttonsContainer.add(this.buttonDeleteTemp);

            this.updateSelectedState(config.state.selected);
            this.updateSavesCounter(gameConfig.saves);
        }

        // Confirmation messages.
        var helpTextWidth = config.buttons.size.width;
        var helpTextHeight = config.help.text.height;
        var helpTextX = 0;
        var helpTextY = config.help.text.y;
        var helpTextMargin = config.help.text.margin;

        this.helpTextBackground = scene.add.graphics();
        this.helpTextBackground.fillStyle(config.help.defaultBackgroundColor, 1.0);
        this.helpTextBackground.fillRoundedRect(helpTextX, helpTextY, helpTextWidth, helpTextHeight, 10);
        
        this.helpTextBackgroundOk = scene.add.graphics();
        this.helpTextBackgroundOk.fillStyle(config.help.okBackgroundColor, 1.0);
        this.helpTextBackgroundOk.fillRoundedRect(helpTextX, helpTextY, helpTextWidth, helpTextHeight, 10);

        this.helpText = scene.add.text(helpTextX + helpTextWidth/2, helpTextY + helpTextHeight/2, '', config.help.text.style);
        this.helpText.setWordWrapWidth(helpTextWidth - helpTextMargin*2);
        this.helpText.setMaxLines(3);
        this.helpText.setOrigin(0.5, 0.5);

        this.buttonHelpModeValidate = new BButton(this, buttonPositionX, helpTextY + helpTextHeight + 60, null, window.gameConfig.help.buttons.validate, '', 0);
        this.buttonHelpModeValidate.setAction(function(e) {
            if (!scene.helpModeEnabled) {
                return;
            }

            switch (scene.helpType) {
                case config.help.mode.fixSquares:
                    scene.useHelpFixSquares();
                    break;
                case config.help.mode.revealRowOrCol:
                    scene.useHelpRevealRowOrColumn();
                    scene.disableHelpMode(e, true);
                    break;
                case config.help.mode.revealSquare:
                    scene.useHelpRevealSquare();
                    scene.disableHelpMode(e, true);
                    break;
                case config.help.mode.save:
                    scene.saveGame(e);
                    scene.disableHelpMode(e, true);
                    break;
                case config.help.mode.reset:
                    if (gameConfig.createMode) {
                        scene.clearCells(e);
                    } else {
                        scene.resetGame(e);
                    }
                    scene.disableHelpMode(e, true);
                    break;
                default:
                    scene.disableHelpMode(e, true);
                    break;
            }
        });

        this.buttonHelpModeCancel = new BButton(this, buttonPositionX, this.buttonHelpModeValidate.y + space, null, window.gameConfig.help.buttons.cancel, '', 0);
        this.buttonHelpModeCancel.setAction(function(e) {
            scene.disableHelpMode(e, true);
        });

        this.buttonsContainer.addAt(this.helpTextBackground, 30);
        this.buttonsContainer.addAt(this.helpTextBackgroundOk, 31);
        this.buttonsContainer.addAt(this.helpText, 40);
        this.buttonsContainer.addAt(this.buttonHelpModeValidate, 30);
        this.buttonsContainer.addAt(this.buttonHelpModeCancel, 30);

        this.disableHelpMode(false);

        this.scaleButtons();
        
        // Events.
        this.input.mouse.disableContextMenu();

        this.grid.background.on('pointermove', function(e) {
            scene.highlightCell(e);
        });
        this.grid.background.on('pointerout', function(e) {
            scene.stopHighlightingCell(e);
        });
        this.grid.background.on('pointerdown', function(e) {
            scene.startColoring(e);
        });
        this.grid.container.on('pointerdown', function(e) {
            scene.selectRowOrColumn(e);
        });
        this.input.on('pointerup', function(e) {
            scene.stopColoring(e);
            scene.stopExtendingGrid(e);
        });
        this.input.on('drag', function(e, gameObject, dragX, dragY) {
            if (gameObject.name == 'gridContainer') {
                gameObject.x = dragX;
                gameObject.y = dragY;
                return
            }

            var gridTL = scene.grid.border.getTopLeft();
            var gridBR = scene.grid.border.getBottomRight();

            var gridBoldLineWidth = config.grid.line.width.bold - config.grid.line.width.thin;

            switch (gameObject.name) {
                case 'lHandle':
                    // dark magic : scale the distance of the dragging action.
                    gameObject.x = (scene.extendGrid.startDragging + (e.event.layerX - scene.extendGrid.startDragging) - scene.grid.container.x) / scene.grid.container.scaleX;
    
                    var currentCountOfBolderLinesInGrid = Math.floor((gameConfig.cols.count - 1)/config.grid.cell.groupSize);
                    var maxX = gridBR.x - config.grid.border.width - (config.grid.size.minimum.cols * scene.cellSize) - (currentCountOfBolderLinesInGrid * gridBoldLineWidth) - config.grid.extend.handle.margin;
                    if (gameObject.x > maxX) {
                        gameObject.x = maxX;
                    }
    
                    var maxCountOfBolderLinesInGrid = Math.floor((config.grid.size.maximum.cols - 1)/config.grid.cell.groupSize);
                    var minX = gridBR.x - (config.grid.border.width * 2) - (config.grid.size.maximum.cols * scene.cellSize) - (maxCountOfBolderLinesInGrid * gridBoldLineWidth) - config.grid.extend.handle.margin;
                    if (gameObject.x < minX) {
                        gameObject.x = minX;
                    }

                    scene.grid.rowHintsContainer.x = gameObject.x - config.grid.extend.hints.margin/2;
                    maxX = gridTL.x - config.grid.hints.rowSpacing;
                    if (scene.grid.rowHintsContainer.x > maxX) {
                        scene.grid.rowHintsContainer.x = maxX;
                    }
    
                    scene.extendGrid.count = Math.floor((gridTL.x - gameObject.x - config.grid.extend.handle.margin + 1) / scene.cellSize);
                    var countOfBolderLinesInRect = Math.floor(Math.abs(scene.extendGrid.count)/config.grid.cell.groupSize);
                    scene.extendGrid.rect.setSize(((scene.cellSize * scene.extendGrid.count) - (countOfBolderLinesInRect * gridBoldLineWidth)) * -1, scene.extendGrid.rect.height);
                    scene.extendGrid.rect.setFillStyle(scene.extendGrid.count > 0 ? config.grid.extend.moreColor : config.grid.extend.lessColor);
                    scene.updateHandleLabel('l');
                    break;

                case 'rHandle':
                    // dark magic : scale the distance of the dragging action.
                    gameObject.x = (scene.extendGrid.startDragging + (e.event.layerX - scene.extendGrid.startDragging) - scene.grid.container.x) / scene.grid.container.scaleX;
    
                    var currentCountOfBolderLinesInGrid = Math.floor((gameConfig.cols.count - 1)/config.grid.cell.groupSize);
                    var minX = gridTL.x + (config.grid.border.width * 2) + (config.grid.size.minimum.cols * scene.cellSize) + (currentCountOfBolderLinesInGrid * gridBoldLineWidth) + config.grid.extend.handle.margin;
                    if (gameObject.x < minX) {
                        gameObject.x = minX;
                    }
    
                    var maxCountOfBolderLinesInGrid = Math.floor((config.grid.size.maximum.cols - 1)/config.grid.cell.groupSize);
                    var maxX = gridTL.x + (config.grid.border.width * 2) + (config.grid.size.maximum.cols * scene.cellSize) + (maxCountOfBolderLinesInGrid * gridBoldLineWidth) + config.grid.extend.handle.margin;
                    if (gameObject.x > maxX) {
                        gameObject.x = maxX;
                    }
    
                    scene.extendGrid.count = Math.floor((gameObject.x - gridBR.x - config.grid.extend.handle.margin + 1) / scene.cellSize);
                    var countOfBolderLinesInRect = Math.floor(Math.abs(scene.extendGrid.count)/config.grid.cell.groupSize);
                    scene.extendGrid.rect.setSize((scene.cellSize * scene.extendGrid.count) - (countOfBolderLinesInRect * gridBoldLineWidth), scene.extendGrid.rect.height);
                    scene.extendGrid.rect.setFillStyle(scene.extendGrid.count > 0 ? config.grid.extend.moreColor : config.grid.extend.lessColor);
                    scene.updateHandleLabel('r');
                    break;

                case 'tHandle':
                    // dark magic : scale the distance of the dragging action.
                    gameObject.y = (scene.extendGrid.startDragging + (e.event.layerY - scene.extendGrid.startDragging) - scene.grid.container.y) / scene.grid.container.scaleY;
    
                    var currentCountOfBolderLinesInGrid = Math.floor((gameConfig.rows.count - 1)/config.grid.cell.groupSize);
                    var maxY = gridBR.y - config.grid.border.width - (config.grid.size.minimum.rows * scene.cellSize) - (currentCountOfBolderLinesInGrid * gridBoldLineWidth) - config.grid.extend.handle.margin;
                    if (gameObject.y > maxY) {
                        gameObject.y = maxY;
                    }
    
                    var maxCountOfBolderLinesInGrid = Math.floor((config.grid.size.maximum.rows - 1)/config.grid.cell.groupSize);
                    var minY = gridBR.y - (config.grid.border.width * 2) - (config.grid.size.maximum.rows * scene.cellSize) - (maxCountOfBolderLinesInGrid * gridBoldLineWidth) - config.grid.extend.handle.margin;
                    if (gameObject.y < minY) {
                        gameObject.y = minY;
                    }

                    scene.grid.colHintsContainer.y = gameObject.y - config.grid.extend.hints.margin/2;
                    maxY = gridTL.y - config.grid.hints.colSpacing;
                    if (scene.grid.colHintsContainer.y > maxY) {
                        scene.grid.colHintsContainer.y = maxY;
                    }
    
                    scene.extendGrid.count = Math.floor((gridTL.y - gameObject.y - config.grid.extend.handle.margin + 1) / scene.cellSize);
                    var countOfBolderLinesInRect = Math.floor(Math.abs(scene.extendGrid.count)/config.grid.cell.groupSize);
                    scene.extendGrid.rect.setSize(scene.extendGrid.rect.width, ((scene.cellSize * scene.extendGrid.count) - (countOfBolderLinesInRect * gridBoldLineWidth)) * -1);
                    scene.extendGrid.rect.setFillStyle(scene.extendGrid.count > 0 ? config.grid.extend.moreColor : config.grid.extend.lessColor);
                    scene.updateHandleLabel('t');
                    break;

                case 'bHandle':
                    // dark magic : scale the distance of the dragging action.
                    gameObject.y = (scene.extendGrid.startDragging + (e.event.layerY - scene.extendGrid.startDragging) - scene.grid.container.y) / scene.grid.container.scaleY;
    
                    var currentCountOfBolderLinesInGrid = Math.floor((gameConfig.rows.count - 1)/config.grid.cell.groupSize);
                    var minY = gridTL.y + (config.grid.border.width * 2) + (config.grid.size.minimum.rows * scene.cellSize) + (currentCountOfBolderLinesInGrid * gridBoldLineWidth) + config.grid.extend.handle.margin;
                    if (gameObject.y < minY) {
                        gameObject.y = minY;
                    }
    
                    var maxCountOfBolderLinesInGrid = Math.floor((config.grid.size.maximum.rows - 1)/config.grid.cell.groupSize);
                    var maxY = gridTL.y + (config.grid.border.width * 2) + (config.grid.size.maximum.rows * scene.cellSize) + (maxCountOfBolderLinesInGrid * gridBoldLineWidth) + config.grid.extend.handle.margin;
                    if (gameObject.y > maxY) {
                        gameObject.y = maxY;
                    }
    
                    scene.extendGrid.count = Math.floor((gameObject.y - gridBR.y - config.grid.extend.handle.margin + 1) / scene.cellSize);
                    var countOfBolderLinesInRect = Math.floor(Math.abs(scene.extendGrid.count)/config.grid.cell.groupSize);
                    scene.extendGrid.rect.setSize(scene.extendGrid.rect.width, (scene.cellSize * scene.extendGrid.count) - (countOfBolderLinesInRect * gridBoldLineWidth));
                    scene.extendGrid.rect.setFillStyle(scene.extendGrid.count > 0 ? config.grid.extend.moreColor :  config.grid.extend.lessColor);
                    scene.updateHandleLabel('b');
                    break;
            
                default:
                    break;
            }
        });

        // Resize.
        this.events.on('resize', function(width, height) {
            this.cameras.resize(width, height);
        });

        // Create some hint texts.
        var count = (config.grid.size.maximum.cols + config.grid.size.maximum.rows) * 2;
        for (var i=0; i<count; i++) {
            var text = this.createHintText();
            this.unusedHints.push(text);
        }

        // Enable/disable the grid events when the settings modal is shown.
        var buttons = [
            this.buttonHelpFixSquares,
            this.buttonHelpRevealRowOrCol,
            this.buttonHelpRevealSquare,
            this.buttonReset,
            this.buttonSave,
            this.buttonCheck,
            this.buttonHistoryBack,
            this.buttonHistoryForward,
            this.buttonDeleteTemp,
            this.buttonValidateTemp,
            this.buttonCellSelected,
            this.buttonCellSelectedTemp,
            this.buttonCellUnselected,
            this.buttonCellUnselectedTemp,
            this.buttonSettings,
            this.buttonZoomIn,
            this.buttonZoomOut,
            this.buttonCheckLogical,
            this.buttonInvertCells,
            this.buttonImageToPuzzle,
        ];

        $('#'+ config.settings.id).off('show.bs.modal').on('show.bs.modal', function() {
            scene.input.disable(scene.grid.background);
            scene.input.disable(scene.grid.container);

            for (var i = 0; i < buttons.length; i++) {
                var button = buttons[i];
                if (!_.isUndefined(button)) {
                    scene.input.disable(buttons[i]);
                }
            }
        });
        $('#'+ config.settings.id).off('hidden.bs.modal').on('hidden.bs.modal', function() {
            scene.input.enable(scene.grid.background);
            scene.updateDraggableGrid();

            for (var i = 0; i < buttons.length; i++) {
                var button = buttons[i];
                if (!_.isUndefined(button)) {
                    scene.input.enable(buttons[i]);
                }
            }
        });
    }

    updateDraggableGrid() {
        if (config.grid.draggable) {
            this.input.setDraggable(this.grid.container, true);
            this.input.enable(this.grid.container);
        } else {
            this.input.setDraggable(this.grid.container, false);
            this.input.disable(this.grid.container);
        }
    }

    selectRowOrColumn(e) {
        if (!this.helpModeEnabled || this.helpType != config.help.mode.revealRowOrCol) {
            return;
        }

        // Get the coordinates.
        var c = this.getXYFromEventPosition(e);
        var x = c.x;
        var y = c.y;

        var isRow = (x < 0 && y >= 0 && y < gameConfig.rows.count);
        var isColumn = (x >= 0 && y < 0 && x < gameConfig.cols.count);

        if (!isRow && !isColumn) {
            return;
        }

        this.helpSelectRowOrColumn(isRow ? y : x, isRow)
    }

    centerGrid() {
        var minHintX = 0;
        var minHintY = 0;

        for (var i=0; i<this.grid.hints.rows.length; i++) {
            var texts = this.grid.hints.rows[i]
            var firstText = texts[texts.length-1];

            if (firstText.x < minHintX) {
                minHintX = firstText.x;
            }
        }
        for (var i=0; i<this.grid.hints.cols.length; i++) {
            var texts = this.grid.hints.cols[i]
            var firstText = texts[texts.length-1];

            if (firstText.y < minHintY) {
                minHintY = firstText.y;
            }
        }

        var gridWidth = gameConfig.cols.count*config.grid.cell.size + (gameConfig.cols.count-1)*config.grid.line.width.thin;
        var gridHeight = gameConfig.rows.count*config.grid.cell.size + (gameConfig.rows.count-1)*config.grid.line.width.thin;

        var previewWidth = (config.preview.enabled && !config.preview.fixed ? this.grid.preview.border.width * config.preview.scale : 0);
        var previewHeight = (config.preview.enabled && !config.preview.fixed ? this.grid.preview.border.height * config.preview.scale : 0);
        var handleWidth = (gameConfig.createMode ? this.grid.handles.right.width : 0);
        var handleHeight = (gameConfig.createMode ? this.grid.handles.bottom.height : 0);
        var width = gridWidth + Math.max(previewWidth, Math.abs(minHintX)) + (handleWidth * 2);
        var height = gridHeight + Math.max(previewHeight, Math.abs(minHintY)) + (handleHeight * 2);

        var scale = config.grid.zoom.maximum;
        var sceneWidth = this.menuContainer.x;
        var sceneHeight = config.scene.viewPort.height;
        while ((width * scale > sceneWidth || height * scale > sceneHeight) && scale > config.grid.zoom.minimum) {
            scale -= config.grid.zoom.step;
        }
        if (scale > config.grid.zoom.minimum) {
            scale -= config.grid.zoom.step;
        }

        this.grid.container.setScale(scale);

        this.grid.container.setPosition(
            (sceneWidth - (width * scale))/2 + Math.max(previewWidth, Math.abs(minHintX))*scale + (gameConfig.createMode ? this.grid.handles.right.width*scale : 0),
            (sceneHeight - (height * scale))/2 + Math.max(previewHeight, Math.abs(minHintY))*scale + (gameConfig.createMode ? this.grid.handles.bottom.height*scale : 0)
        );
    }

    updateGridShape() {
        // Update the size of the grid.
        var countOfSectionsX = Math.ceil(gameConfig.cols.count/config.grid.cell.groupSize);
        var countOfSectionsY = Math.ceil(gameConfig.rows.count/config.grid.cell.groupSize);
        var countOfBoldLinesX = countOfSectionsX-1;
        var countOfBoldLinesY = countOfSectionsY-1;
        var countOfThinLinesX = gameConfig.cols.count-1-countOfBoldLinesX;
        var countOfThinLinesY = gameConfig.rows.count-1-countOfBoldLinesY;
        var gridWidth = gameConfig.cols.count*config.grid.cell.size + countOfThinLinesX*config.grid.line.width.thin + countOfBoldLinesX*config.grid.line.width.bold;
        var gridHeight = gameConfig.rows.count*config.grid.cell.size + countOfThinLinesY*config.grid.line.width.thin + countOfBoldLinesY*config.grid.line.width.bold;

        this.grid.border.setSize(gridWidth + 2*config.grid.border.width, gridHeight + 2*config.grid.border.width);
        this.grid.background.setSize(gridWidth, gridHeight);
        this.grid.background.setInteractive({ cursor: 'pointer' });
        this.grid.background.input.hitArea.setSize(gridWidth, gridHeight);

        var gridTL = this.grid.background.getTopLeft();
        var gridBR = this.grid.background.getBottomRight();
        
        // Update the alternate backgrounds.
        for (var i=0; i<this.grid.alternateBackgrounds.length; i++) {
            var alternateBackground = this.grid.alternateBackgrounds[i];
            var alternateBackgroundTL = alternateBackground.getTopLeft();

            if (alternateBackgroundTL.x >= gridTL.x && alternateBackgroundTL.y >= gridTL.y && alternateBackgroundTL.x <= gridBR.x && alternateBackgroundTL.y <= gridBR.y) {
                var width = (alternateBackgroundTL.x + this.altBGSize <= gridBR.x ? this.altBGSize : gridBR.x - alternateBackgroundTL.x);
                var height = (alternateBackgroundTL.y + this.altBGSize <= gridBR.y ? this.altBGSize : gridBR.y - alternateBackgroundTL.y);
                alternateBackground.setSize(width, height);
                alternateBackground.setVisible(true);
            } else {
                alternateBackground.setVisible(false);
            }
        }
        
        // Update the lines.
        var decalX = 0;
        for (var i=0; i<this.grid.lines.cols.length; i++) {
            var line = this.grid.lines.cols[i];
            if (i < gameConfig.cols.count - 1) {
                var s = 0;
                if ((i+1)%config.grid.cell.groupSize == 0) {
                    decalX += (config.grid.line.width.bold-config.grid.line.width.thin);
                    s = config.grid.line.width.bold/2;
                }
                var x = gridTL.x + this.cellSize*(i+1) + decalX - s;
                line.setTo(x, gridTL.y, x, gridBR.y);
                line.setVisible(true);
            } else {
                line.setVisible(false);
            }
        }
        var decalY = 0;
        for (var i=0; i<this.grid.lines.rows.length; i++) {
            var line = this.grid.lines.rows[i];
            if (i < gameConfig.rows.count - 1) {
                var s = 0;
                if ((i+1)%config.grid.cell.groupSize == 0) {
                    decalY += (config.grid.line.width.bold-config.grid.line.width.thin);
                    s = config.grid.line.width.bold/2;
                }
                var y = gridTL.y + this.cellSize*(i+1) + decalY - s;
                line.setTo(gridTL.x, y, gridBR.x, y);
                line.setVisible(true);
            } else {
                line.setVisible(false);
            }
        }

        // Update the handles.
        this.updateHandles();
    }

    updatePreviewShape() {
        if (!config.preview.enabled) {
            return;
        }

        var width = gameConfig.cols.count * config.preview.cell.size;
        var height = gameConfig.rows.count * config.preview.cell.size;

        this.grid.preview.border.setSize(width + 2*config.preview.border.width, height + 2*config.preview.border.width);
        this.grid.preview.background.setSize(width, height);
        
        this.updatePreviewPosition();
    }

    updateGridCells() {
        for (var y=0; y<config.grid.size.maximum.rows; y++) {
            for (var x=0; x<config.grid.size.maximum.cols; x++) {
                var cell = this.cellAt(x, y);

                if (x >= gameConfig.cols.count || y >= gameConfig.rows.count) {
                    cell.setVisible(false);
                    this.updatePreviewCell(x, y, config.state.unknown);
                } else {
                    var state = this.stateOfCell(x, y);
                    this.updateCell(x, y, state, false);
                }
            }
        }
    }

    updatePreviewPosition() {
        if (config.preview.fixed) {
            if (this.grid.container.exists(this.grid.preview.container)) {
                this.grid.container.remove(this.grid.preview.container);
            }
            this.add.existing(this.grid.preview.container);

            var width = this.buttonSettings.width*this.buttonSettings.scaleX;
            var height = this.buttonSettings.height*this.buttonSettings.scaleY;
            var scale = config.preview.scale * (height/this.grid.preview.border.height);

            this.grid.preview.container.setScale(scale);
            this.grid.preview.container.setPosition(0.0, 1.0);
            this.grid.preview.container.setX(config.scene.size.margin);
            this.grid.preview.container.setY(config.scene.size.margin);
        } else {
            if (!this.grid.container.exists(this.grid.preview.container)) {
                this.grid.container.add(this.grid.preview.container);
            }

            this.grid.preview.container.setScale(config.preview.scale);
            this.grid.preview.container.setPosition(0.5, 0.5);

            var gridTL = this.grid.background.getTopLeft();
            var width = gameConfig.cols.count * config.preview.cell.size;
            var height = gameConfig.rows.count * config.preview.cell.size;
    
            this.grid.preview.container.setX(gridTL.x - width*config.preview.scale - (3.5*config.preview.scale));
            this.grid.preview.container.setY(gridTL.y - height*config.preview.scale - (3.5*config.preview.scale));
        }
    }

    addHintTexts() {
        for (var i=0; i<gameConfig.rows.hints.length; i++) {
            this.updateRowHintText(i);
            if (!gameConfig.createMode) {
                this.updateRowHintTextColor(i, false);
            }
        }

        for (var i=0; i<gameConfig.cols.hints.length; i++) {
            this.updateColHintText(i);
            if (!gameConfig.createMode) {
                this.updateColHintTextColor(i, false);
            }
        }
    }

    hideColHintTexts(cols, toLeft) {
        if (toLeft) {
            for (var x=0; x<gameConfig.cols.count; x++) {
                this.updateColHintText(x);
            }
        }

        var begin = gameConfig.cols.count;
        var end = begin + cols;

        for (var x=begin; x<end; x++) {
            this.updateColHintText(x);

            var texts = this.grid.hints.cols[x];
            for (var i=0; i<texts.length; i++) {
                texts[i].setVisible(false);
            }
        }
    }

    hideRowHintTexts(rows, toTop) {
        if (toTop) {
            for (var y=0; y<gameConfig.rows.count; y++) {
                this.updateRowHintText(y);
            }
        }

        var begin = gameConfig.rows.count;
        var end = begin + rows;

        for (var y=begin; y<end; y++) {
            this.updateRowHintText(y);

            var texts = this.grid.hints.rows[y];
            for (var i=0; i<texts.length; i++) {
                texts[i].setVisible(false);
            }
        }
    }

    showColHintTexts(cols, toLeft) {
        var end = gameConfig.cols.count;
        var start = (toLeft ? 0 : end - cols);

        for (var x=start; x<end; x++) {
            this.updateColHintText(x);

            var texts = this.grid.hints.cols[x];
            for (var i=0; i<texts.length; i++) {
                texts[i].setVisible(true);
            }
        }
    }

    showRowHintTexts(rows, toTop) {
        var end = gameConfig.rows.count;
        var start = (toTop ? 0 : end - rows);

        for (var y=start; y<end; y++) {
            this.updateRowHintText(y);

            var texts = this.grid.hints.rows[y];
            for (var i=0; i<texts.length; i++) {
                texts[i].setVisible(true);
            }
        }
    }

    updateAllHintTexts() {
        for (var x = 0; x < gameConfig.cols.count; x++) {
            this.updateColHintTextColor(x, false);
        }

        for (var y = 0; y < gameConfig.rows.count; y++) {
            this.updateRowHintTextColor(y, false);
        }
    }

    updateColHintText(i, highlighted = false) {
        if (this.grid.hints.cols.length > i) {
            while (this.grid.hints.cols[i].length > 0) {
                var text = this.grid.hints.cols[i].pop();
                this.enqueueHintText(text);
            }
        }

        var gridTL = this.grid.background.getTopLeft();
        var x = gridTL.x + config.grid.cell.size/2 + ((config.grid.cell.size+config.grid.line.width.thin) * i) + ((config.grid.line.width.bold-config.grid.line.width.thin) * Math.floor(i/config.grid.cell.groupSize));

        var theHints = (gameConfig.cols.hints.length > i ? gameConfig.cols.hints[i].split(',') : ['0']);
        var texts = []; 

        var maxHeight = 0;
        for (var j=theHints.length-1; j>=0; j--) {
            var text = this.dequeueHintText();

            var y = gridTL.y - 8 - ((text.height + config.grid.hints.colSpacing) * (theHints.length - 1 - j));
            var height = Math.abs(y) + text.height + config.grid.hints.colSpacing;
            if ( height > maxHeight) {
                maxHeight = height;
            }

            text.setOrigin(0.5, 1);
            text.setPosition(x, y);
            text.setText(theHints[j]);
            text.setVisible(true);
            this.grid.colHintsContainer.add(text);

            if (highlighted) {
                text.setColor(config.grid.hints.highlightedColor);
            }

            texts.push(text);
        }

        this.grid.hints.cols[i] = texts;

        if (maxHeight > this.grid.highlightedBackgroundHintCol.height) {
            this.grid.highlightedBackgroundHintCol.setSize(this.cellSize, maxHeight + (gameConfig.createMode ? config.grid.extend.hints.margin : 0));
            this.grid.highlightedBackgroundHintCol.setOrigin(0.0, 1.0);
        }
    }

    updateRowHintText(i, highlighted = false) {
        if (this.grid.hints.rows.length > i) {
            while (this.grid.hints.rows[i].length > 0) {
                var text = this.grid.hints.rows[i].pop();
                this.enqueueHintText(text);
            }
        }

        var gridTL = this.grid.background.getTopLeft();
        var y = gridTL.y + config.grid.cell.size/2 + ((config.grid.cell.size+config.grid.line.width.thin) * i) + ((config.grid.line.width.bold-config.grid.line.width.thin) * Math.floor(i/config.grid.cell.groupSize));

        var theHints = (gameConfig.rows.hints.length > i ? gameConfig.rows.hints[i].split(',') : ['0']);
        var texts = []; 

        var nextPositionX = gridTL.x - 10;
        for (var j=theHints.length-1; j>=0; j--) {
            var text = this.dequeueHintText();
            text.setOrigin(1, 0.5);
            text.setPosition(nextPositionX, y);
            text.setText(theHints[j]);
            text.setVisible(true);
            this.grid.rowHintsContainer.add(text);

            if (highlighted) {
                text.setColor(config.grid.hints.highlightedColor);
            }

            texts.push(text);

            nextPositionX -= (text.width + config.grid.hints.rowSpacing);
        }

        this.grid.hints.rows[i] = texts;

        var width = Math.abs(nextPositionX) - gridTL.x;
        if (width > this.grid.highlightedBackgroundHintRow.width) {
            this.grid.highlightedBackgroundHintRow.setSize(width + (gameConfig.createMode ? config.grid.extend.hints.margin : 0), this.cellSize);
            this.grid.highlightedBackgroundHintRow.setOrigin(1.0, 0.0);
        }
    }
    
    updateColHintTextColor(index, isHighlighted = true) {
        if (index >= gameConfig.cols.hints.length) {
            return;
        }

        // The hints defined by the model.
        var theHints = gameConfig.cols.hints[index].split(',');

        // The hints defined by the cells of the grid.
        var gridHints = this.buildColHints(index).split(',');

        // The UI texts of the grid.
        var hintTexts = this.grid.hints.cols[index];

        // Reset the hint texts.
        for (var i = 0; i < hintTexts.length; i++) {
            hintTexts[i].setName('n');
            this.colorHintText(hintTexts[i], isHighlighted);
        }
	            
        var start = 0;
        for (var i = 0; i < gridHints.length; i++) {
            var s = gridHints[i];
            for (var j = start; j < theHints.length; j++) {
                var l = theHints[j];
                if (s == l) {
                    var hintText = hintTexts[hintTexts.length - 1 - j];
                    hintText.setName('d');
                    this.colorHintText(hintText, isHighlighted);

                    start = j + 1;
                    j = theHints.length;
                }
            }
        }
    }
    
    updateRowHintTextColor(index, isHighlighted = true) {
        if (index >= gameConfig.rows.hints.length) {
            return;
        }

        // The hints defined by the model.
        var theHints = gameConfig.rows.hints[index].split(',');

        // The hints defined by the cells of the grid.
        var gridHints = this.buildRowHints(index).split(',');

        // The UI texts of the grid.
        var hintTexts = this.grid.hints.rows[index];

        // Reset the hint texts.
        for (var i = 0; i < hintTexts.length; i++) {
            hintTexts[i].setName('n');
            this.colorHintText(hintTexts[i], isHighlighted);
        }
	            
        var start = 0;
        for (var i = 0; i < gridHints.length; i++) {
            var s = gridHints[i];
            for (var j = start; j < theHints.length; j++) {
                var l = theHints[j];
                if (s == l) {
                    var hintText = hintTexts[hintTexts.length - 1 - j];
                    hintText.setName('d');
                    this.colorHintText(hintText, isHighlighted);

                    start = j + 1;
                    j = theHints.length;
                }
            }
        }
    }

    updateCellsColorAtRow(index, isHighlighted = true) {
        if (!config.grid.highlightRowAndCol) {
            return;
        }

        var alpha = (isHighlighted ? 0.65 : 1.0);
        for (var i=0; i<gameConfig.cols.count; i++) {
            var cell = this.cellAt(index, i);
            cell.setAlpha(alpha);
            if (isHighlighted) {
                cell.setTint(config.grid.cell.highlightTint);
            } else {
                cell.clearTint();
            }
        }
    }

    updateCellsColorAtCol(index, isHighlighted = true) {
        if (!config.grid.highlightRowAndCol) {
            return;
        }

        var alpha = (isHighlighted ? 0.65 : 1.0);
        for (var i=0; i<gameConfig.rows.count; i++) {
            var cell = this.cellAt(i, index);
            cell.setAlpha(alpha);
            if (isHighlighted) {
                cell.setTint(config.grid.cell.highlightTint);
            } else {
                cell.clearTint();
            }
        }
    }

    startTimer() {
        if (window.gameConfig.createMode || this.stopped || !isPuzzleUIPresent()) {
            return
        }

        var t = this;
        setTimeout(function(){
            t.updateTimer();
            t.startTimer();
        }, 1001);
    }

    updateTimer() {
        if (window.gameConfig.createMode || this.stopped || !isPuzzleUIPresent()) {
            return
        }

        var elapsedSeconds = getElapsedSeconds();
        window.gameConfig.elapsedSeconds = elapsedSeconds;
        var h = parseInt(elapsedSeconds/3600, 10);
        if (h <= 0) {
            h = '';
        } else {
            elapsedSeconds -= h*3600;
            if (h < 10) {
                h = '0' + h;
            }
            h += ':'
        }
        var min = parseInt(elapsedSeconds/60, 10);
        if (min < 10) {
            min = '0' + min;
        }
        var sec = (elapsedSeconds%60);
        if (sec < 10) {
            sec = '0' + sec;
        }
        
        this.timer.setText(h+min+':'+sec);
    }

    updateHelpText(text) {
        this.helpText.setText(text);
    }

    gridAlpha(alpha) {
        this.grid.border.setAlpha(alpha);
        for (var i = 0; i < this.grid.lines.cols.length; i++) {
            this.grid.lines.cols[i].setAlpha(alpha);
        }
        for (var i = 0; i < this.grid.lines.rows.length; i++) {
            this.grid.lines.rows[i].setAlpha(alpha);
        }
    }
    
    enableHelpMode(type, animated) {
        var buttons = [
            this.buttonHelpFixSquares,
            this.buttonHelpRevealRowOrCol,
            this.buttonHelpRevealSquare,
            this.buttonReset,
            this.buttonSave,
            this.buttonCheck,
            this.buttonHistoryBack,
            this.buttonHistoryForward,
            this.buttonDeleteTemp,
            this.buttonValidateTemp,
            this.buttonCellSelected,
            this.buttonCellSelectedTemp,
            this.buttonCellUnselected,
            this.buttonCellUnselectedTemp,
            this.buttonCheckLogical,
            this.buttonImageToPuzzle,
            this.buttonInvertCells
        ];

        for (var i = 0; i < buttons.length; i++) {
            if (!_.isUndefined(buttons[i])) {
                buttons[i].hide();
            }
        }

        switch (type) {
            case config.help.mode.revealRowOrCol:
                this.updateHelpText(gameConfig.help.revealRowOrColumn.description);
                gameConfig.cellMode = config.cellMode.none;
                break
            case config.help.mode.revealSquare:
                this.updateHelpText(gameConfig.help.revealSquare.description);
                gameConfig.cellMode = config.cellMode.help;
                break
            case config.help.mode.fixSquares:
                this.updateHelpText(gameConfig.help.fixSquares.description);
                gameConfig.cellMode = config.cellMode.none;
                break
            case config.help.mode.save:
                this.updateHelpText(gameConfig.help.save.description);
                gameConfig.cellMode = config.cellMode.none;
                break
            case config.help.mode.reset:
                this.updateHelpText(gameConfig.help.reset.description);
                gameConfig.cellMode = config.cellMode.none;
                break
        }

        this.helpText.setVisible(true);
        this.helpTextBackground.setVisible(true);
        this.helpTextBackgroundOk.setVisible(false);
        this.buttonHelpModeCancel.show();
        this.buttonHelpModeValidate.show();

        this.helpModeEnabled = true;
        this.helpType = type;

        this.gridAlpha(0.2);
    }
    
    disableHelpMode(animated) {
        var buttons = [
            this.buttonHelpFixSquares,
            this.buttonHelpRevealRowOrCol,
            this.buttonHelpRevealSquare,
            this.buttonReset,
            this.buttonSave,
            this.buttonCheck,
            this.buttonHistoryBack,
            this.buttonHistoryForward,
            this.buttonDeleteTemp,
            this.buttonValidateTemp,
            this.buttonCellSelected,
            this.buttonCellSelectedTemp,
            this.buttonCellUnselected,
            this.buttonCellUnselectedTemp,
            this.buttonCheckLogical,
            this.buttonImageToPuzzle,
            this.buttonInvertCells
        ];

        for (var i = 0; i < buttons.length; i++) {
            if (!_.isUndefined(buttons[i])) {
                buttons[i].show();
            }
        }

        this.helpText.setVisible(false);
        this.helpTextBackground.setVisible(false);
        this.helpTextBackgroundOk.setVisible(false);
        this.buttonHelpModeCancel.hide();
        this.buttonHelpModeValidate.hide();

        this.helpModeEnabled = false;
        gameConfig.cellMode = config.cellMode.normal;

        this.gridAlpha(1.0);
        this.clearHelpSelectedSquare();
        this.clearHelpSelectedRowOrColumn();
    }

    end(time) {
        this.stopped = true;
        this.add.tween({
            targets: [this.menuContainer,this.grid.preview.container, this.grid.container],
            ease: 'Sine.easeInOut',
            duration: time,
            delay: 0,
            alpha: {
                getStart: () => 1.0,
                getEnd: () => 0.0
            }
        });
    }

    scaleButtons() {
        var height = this.menuContainer.getData('height');

        if (_.isUndefined(height)) {
            return;
        }

        var maxHeight = config.scene.viewPort.height;
        var scale = (height > maxHeight ? maxHeight/height : 1.0);
        if (scale > config.buttons.size.maxScale) {
            scale = config.buttons.size.maxScale;
        }
        this.menuContainer.setScale(scale);
        this.menuContainer.setX(config.scene.size.width - config.buttons.size.width*scale - config.scene.size.margin);
    }
}

//------------------
function initializePuzzleUI(c, parentId) {
    destroyPuzzleUI();

    var settings = getPuzzleSettings();
    config.grid.cell.bordered = settings.borderedCells;
    config.preview.fixed = settings.fixedPreview;
    config.preview.scale = settings.previewScale;
    config.grid.hints.disabledEnabled = settings.coloredHints;
    config.grid.background.contrastLevel = settings.backgroundContrast;
    config.grid.draggable = settings.draggableGrid;
    config.grid.highlightRowAndCol = settings.highlightRowAndCol;

    setSceneSize(window.innerWidth, window.innerHeight);

    var $colorPicker = $('#' + config.settings.colorPickerId);
    var backgroundColor = $colorPicker.attr('value');
    if (!backgroundColor.startsWith('#')) {
        backgroundColor = '#ffffff';
    }

    if (_.isUndefined(window.puzzleContainer)) {
        var scale = (config.scene.size.scale*100);
        var style = {
            'position': 'absolute',
            'top': ((100 - scale)/2) + '%',
            'left': ((100 - scale)/2) + '%',
            'z-index': '100',
            'width': scale + '%',
            'height': scale + '%',
            'background-color': '#ffffff'
        };

        window.puzzleContainer = $('#' + parentId)
            .css(style)
            .appendTo($('body'));

        window.puzzleLoading = $('<div></div>')
            .attr('id', config.scene.loadingId)
            .css(style)
            .html(c.text.loading);
            
    }
        
    if (_.isUndefined(window.puzzleAd)) {
        window.puzzleAdTo = $('<div></div>')
            .css('position', 'fixed')
            .css('bottom', 0)
            .css('left', 0)
            .css('z-index', '101')
            .appendTo($('body'));

        window.puzzleAd = $('#' + config.scene.addId);
        window.puzzleAdFrom = window.puzzleAd.parent();
    }

    window.puzzleLoading.css('background-color', backgroundColor);

    showGameUI();
    showGameLoading();

    window.gameConfig = c;
    window.gameConfig.cellMode = config.cellMode.normal

    if (_.isUndefined(window.gameConfig.time)) {
        window.gameConfig.time = Date.now();
    }
    if (_.isUndefined(window.gameConfig.prevtime)) {
        window.gameConfig.prevtime = 0;
    }
    window.gameConfig.pause = 0;

    window.gameUI = new Phaser.Game({
        type: Phaser.AUTO,
        scene: [GameScene],
        backgroundColor: parseInt(Number(backgroundColor.replace('#', '0x')), 10),
        resolution: window.devicePixelRatio,
        scale: {
            mode: Phaser.Scale.NONE,
            autoCenter: Phaser.Scale.CENTER_HORIZONTALLY,
            parent: parentId,
            width: config.scene.size.width,
            height: config.scene.size.height
        }
    });
    
    window.addEventListener('resize', resizeGameUI);

    $colorPicker.spectrum({
        type: 'color',
        showPaletteOnly: 'true',
        togglePaletteOnly: 'true',
        showInitial: 'true',
        showAlpha: 'false',
        showButtons: 'false',
        allowEmpty: 'false',
        color: backgroundColor,
        change: function(color) {
            var c = color.toHexString();
            getScene().cameras.main.setBackgroundColor(c);
            $colorPicker.attr('value', c);
        }
    });
}

function destroyPuzzleUI() {
    if (!isPuzzleUIPresent()) {
        return undefined
    }
    
    window.removeEventListener('resize', resizeGameUI);
    window.gameUI.destroy(true);
    window.gameUI = undefined;
    window.puzzleContainer.hide();
    window.puzzleContainer = undefined;
    window.gameConfig = undefined;
}

function resizeGameUI(gameSize, baseSize, displaySize, previousWidth, previousHeight) {
    if (setSceneSize(window.innerWidth, window.innerHeight)) {
        window.gameUI.scale.resize(config.scene.size.width, config.scene.size.height);
        getScene().scaleButtons();
        getScene().centerGrid();
    }
}

function endGame(time) {
    if (!isPuzzleUIPresent()) {
        return
    }
    getScene().end(time);
}

function isPuzzleUIPresent() {
    return !_.isUndefined(window.gameUI);
}

function isTestMode() {
    return !_.isUndefined(window.gameConfig.testMode) && window.gameConfig.testMode;
}

function getPuzzleProperties() {
    if (!isPuzzleUIPresent()) {
        return undefined
    }

    return {
        rows: window.gameConfig.rows,
        cols: window.gameConfig.cols,
        solution: getScene().encodeSolution()
    }
}

function getPuzzleSettings() {
    var settingsForm = $('#' + config.settings.id);
    
    return {
        borderedCells: $('input[name=borderedcells]', settingsForm).is(':checked'),
        fixedPreview: $('input[name=fixedpreview]', settingsForm).is(':checked'),
        coloredHints: $('input[name=coloredhints]', settingsForm).is(':checked'),
        backgroundContrast: parseInt($('input[name=backgroundcontrast]', settingsForm).val(), 10),
        backgroundColor: $('#' + config.settings.colorPickerId).val(),
        previewScale: parseFloat($('input[name=previewscale]', settingsForm).val()),
        draggableGrid: $('input[name=draggablegrid]', settingsForm).is(':checked'),
        highlightRowAndCol: $('input[name=highlightrowandcol]', settingsForm).is(':checked'),
    }
}

function getElapsedSeconds() {
    return Math.round((Date.now() - window.gameConfig.time)/1000 + window.gameConfig.prevtime - window.gameConfig.pause);
}

function getPausedSeconds() {
    return window.gameConfig.pause;
}

function updateSavesCounter(count) {
    if (!isPuzzleUIPresent()) {
        return undefined
    }

    getScene().updateSavesCounter(count);
}

function updateGameHelpRevealRowOrColCounter(count) {
    if (!isPuzzleUIPresent()) {
        return undefined
    }

    getScene().updateGameHelpRevealRowOrColCounter(count);
}

function updateGameHelpRevealSquareCounter(count) {
    if (!isPuzzleUIPresent()) {
        return undefined
    }

    getScene().updateGameHelpRevealSquareCounter(count);
}

function updateGameHelpFixSquaresCounter(count) {
    if (!isPuzzleUIPresent()) {
        return undefined
    }

    getScene().updateGameHelpFixSquaresCounter(count);
}