Skip to content
Snippets Groups Projects
spniCore.js 16.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • /********************************************************************************
    
     This file contains the variables and functions that forms the core of the game.
    
     Anything that is needed game-wide is kept here.
     ********************************************************************************/
    
    /**********************************************************************
     * Game Wide Constants
     **********************************************************************/
    
    /* General Constants */
    
    FarawayVision's avatar
    FarawayVision committed
    var DEBUG = false;
    
    var EPILOGUES_ENABLED = true;
    
    var EPILOGUE_BADGES_ENABLED = true;
    
    var BASE_FONT_SIZE = 14;
    var BASE_SCREEN_WIDTH = 100;
    
    /* Game Wide Constants */
    var HUMAN_PLAYER = 0;
    
    /* Directory Constants */
    var IMG = 'img/';
    
    /*var OPP = 'opponents/';
    #The "OPP" folder abbreviation was used to slightly shorten a few lines in spniSelect that looked for opponents in the opponents folder.
    #Now that opponents can be specified in any folder, this is no longer required.*/
    
    
    /* Gender Images */
    var MALE_SYMBOL = IMG + 'male.png';
    var FEMALE_SYMBOL = IMG + 'female.png';
    
    
    
    
    /* game table */
    var tableOpacity = 1;
    $gameTable = $('#game-table');
    
    /* useful variables */
    var BLANK_PLAYER_IMAGE = "opponents/blank.png";
    
    /* player array */
    
    /* Current timeout ID, so we can cancel it when restarting the game in order to avoid trouble. */
    var timeoutID;
    
    
    /**********************************************************************
     * Game Wide Global Variables
     **********************************************************************/
    
    var table = new Table();
    
    /**********************************************************************
     * Screens & Modals
     **********************************************************************/
    
    /* Screens */
    
    Joseph Kantel's avatar
    Joseph Kantel committed
    $warningScreen = $('#warning-screen');
    
    $titleScreen = $('#title-screen');
    $selectScreen = $('#main-select-screen');
    $individualSelectScreen = $('#individual-select-screen');
    $groupSelectScreen = $('#group-select-screen');
    $gameScreen = $('#game-screen');
    $epilogueScreen = $('#epilogue-screen');
    
    
    /* Modals */
    $searchModal = $('#search-modal');
    
    $groupSearchModal = $('#group-search-modal');
    
    $creditModal = $('#credit-modal');
    $versionModal = $('#version-modal');
    $gameSettingsModal = $('#game-settings-modal');
    
    /* Screen State */
    $previousScreen = null;
    
    
    /********************************************************************************
     * Game Wide Utility Functions
     ********************************************************************************/
    
    
    /* Fetch a possibly compressed file.
     * Attempts to fetch a compressed version of the file first,
     * then fetches the uncompressed version of the file if that isn't found.
     */
    function fetchCompressedURL(baseUrl, successCb, errorCb) {
        /*
         * The usual Jquery AJAX request function doesn't play nice with
         * the binary-encoded data we'll get here, so we do the XHR manually.
         * (I would use fetch() were it not for compatibility issues.)
         */
        var req = new XMLHttpRequest();
        req.open('GET', baseUrl+'.gz', true);
        req.responseType = 'arraybuffer';
        
        req.onload = function(ev) {
            if (req.status < 400 && req.response) {
                var data = new Uint8Array(req.response);
                var decompressed = pako.inflate(data, { to: 'string' });
                successCb(decompressed);
            } else if (req.status === 404) {
                $.ajax({
                    type: "GET",
            		url: baseUrl,
            		dataType: "text",
                    success: successCb,
                    error: errorCb,
                });
            } else {
                errorCb();
            }
        }
        
        req.onerror = function(err) {
            $.ajax({
                type: "GET",
                url: baseUrl,
                dataType: "text",
                success: successCb,
                error: errorCb,
            });
        }
        
        req.send(null);
    }
    
    
    
    /**********************************************************************
     *****                Player Object Specification                 *****
     **********************************************************************/
    
    /************************************************************
    
     * Creates and returns a new player object based on the
    
     * supplied information.
     *
     * folder (string), the path to their folder
     * first (string), their first name.
     * last (string), their last name.
    
     * labels (string or XML element), what's shown on screen and what other players refer to them as.
     *   Can vary by stage.
     * size (string): Their level of endowment
     * intelligence (string or XML element), the name of their AI algorithm.
     *   Can vary by stage.
    
     * gender (constant), their gender.
     * clothing (array of Clothing objects), their clothing.
     * timer (integer), time until forfeit is finished.
     * state (array of PlayerState objects), their sequential states.
     * xml (jQuery object), the player's loaded XML file.
     ************************************************************/
    
    function createNewPlayer (id, first, last, labels, gender, size, intelligence, timer, scale, tags, xml) {
    
        var newPlayerObject = {id:id,
                               folder:'opponents/'+id+'/',
    						   first:first,
    
                               last:last,
    
    						   size:size,
    
    Ell's avatar
    Ell committed
    						   intelligence:intelligence,
    
                               gender:gender,
                               timer:timer,
    
                               tags:tags,
    
                               
                               getImagesForStage: function(stage) {
                                   if(!this.xml) return [];
                                   
                                   var imageSet = {};
                                   var folder = this.folder;
                                   this.xml.find('stage[id="'+stage+'"] state').each(function () {
                                       imageSet[folder+$(this).attr('img')] = true;
                                   });
                                   return Object.keys(imageSet);
                               },
    
                               getByStage: function (arr) {
                                   if (typeof(arr) === "string") {
                                       return arr;
    
                                   }
                                   var bestFitStage = -1;
                                   var bestFit = null;
    
                                   for (var i = 0; i < arr.length; i++) {
                                       var startStage = arr[i].getAttribute('stage');
    
                                       startStage = parseInt(startStage, 10) || 0;
    
                                       if (startStage > bestFitStage && startStage <= this.stage) {
                                           bestFit = $(arr[i]).text();
    
                                   return bestFit;
                               },
                               getIntelligence: function () {
                                   return this.getByStage(this.intelligence) || eIntelligence.AVERAGE;
    
                               updateLabel: function () {
                                   if (this.labels) this.label = this.getByStage(this.labels);
                               }
    
    	initPlayerState(newPlayerObject);
    
        return newPlayerObject;
    }
    
    
    /*******************************************************************
    
    FarawayVision's avatar
    FarawayVision committed
     * (Re)Initialize the player properties that change during a game
    
     *******************************************************************/
    function initPlayerState(player) {
    	player.out = player.finished = player.exposed = false;
    	player.forfeit = "";
    
    	player.stage = player.current = player.consecutiveLosses = 0;
    	player.timeInStage = -1;
    
    	player.markers = {};
    	if (player.xml !== null) {
    
            /* Load in the legacy "start" lines, and also
             * initialize player.chosenState to the first listed line.
             * This may be overridden by later updateBehaviour calls if
             * the player has (new-style) selected or game start case lines.
             */
    
    		player.allStates = parseDialogue(player.xml.find('start'), player);
    
    		player.chosenState = player.allStates[0];
    
    		loadOpponentWardrobe(player);
    	}
    
    /**********************************************************************
     *****              Overarching Game Flow Functions               *****
     **********************************************************************/
    
    /************************************************************
     * Loads the initial content of the game.
     ************************************************************/
    function initialSetup () {
        /* start by creating the human player object */
    
        var humanPlayer = createNewPlayer("human", "", "", "", eGender.MALE, eSize.MEDIUM, eIntelligence.AVERAGE, 20, undefined, [], null);
    
        players[HUMAN_PLAYER] = humanPlayer;
    
    	/* enable table opacity */
    	tableOpacity = 1;
    	$gameTable.css({opacity:1});
    
        /* load the all content */
        loadTitleScreen();
        selectTitleCandy();
    
    	/* Make sure that the config file is loaded before processing the
    	   opponent list, so that includedOpponentStatuses is populated. */
        loadConfigFile().always(loadSelectScreen);
    
    	/* show the title screen */
    
    Joseph Kantel's avatar
    Joseph Kantel committed
    	$warningScreen.show();
    
        autoResizeFont();
    }
    
    
    function loadConfigFile () {
    
            type: "GET",
    		url: "config.xml",
    		dataType: "text",
    		success: function(xml) {           
    
    			var _epilogues = $(xml).find('epilogues').text();
                if(_epilogues.toLowerCase() === 'false') {
                    EPILOGUES_ENABLED = false;
                    console.log("Epilogues are disabled.");
                    $("#title-gallery-edge").hide();
                } else {
                    console.log("Epilogues are enabled.");
                    EPILOGUES_ENABLED = true;
                }
                
                var _epilogue_badges = $(xml).find('epilogue_badges').text();
                if(_epilogue_badges.toLowerCase() === 'false') {
                    EPILOGUE_BADGES_ENABLED = false;
                    console.log("Epilogue badges are disabled.");
                } else {
                    console.log("Epilogue badges are enabled.");
                    EPILOGUE_BADGES_ENABLED = true;
                }
                
    
    			var _debug = $(xml).find('debug').text();
    
                if (_debug === "true") {
                    DEBUG = true;
                    console.log("Debugging is enabled");
                }
                else {
                    DEBUG = false;
                    console.log("Debugging is disabled");
                }
    
    			$(xml).find('include-status').each(function() {
    				includedOpponentStatuses[$(this).text()] = true;
    				console.log("Including", $(this).text(), "opponents");
    			});
    
    Joseph Kantel's avatar
    Joseph Kantel committed
    function enterTitleScreen() {
        $warningScreen.hide();
        $titleScreen.show();
    }
    
    
    /************************************************************
     * Transitions between two screens.
     ************************************************************/
    function screenTransition (first, second) {
    	first.hide();
    	second.show();
    }
    
    /************************************************************
     * Switches to the next screen based on the screen provided.
     ************************************************************/
    function advanceToNextScreen (screen) {
        if (screen == $titleScreen) {
            /* advance to the select screen */
    		screenTransition($titleScreen, $selectScreen);
    
        } else if (screen == $selectScreen) {
            /* advance to the main game screen */
            $selectScreen.hide();
    		loadGameScreen();
            $gameScreen.show();
        }
    }
    
    /************************************************************
     * Switches to the last screen based on the screen provided.
     ************************************************************/
    function returnToPreviousScreen (screen) {
        if (screen == $selectScreen) {
            /* return to the title screen */
            $selectScreen.hide();
            $titleScreen.show();
        }
    }
    
    /************************************************************
    
     * Resets the game state so that the game can be restarted.
    
     ************************************************************/
    
    function resetPlayers () {
    
    	for (var i = 0; i < players.length; i++) {
    
    		if (players[i] != null) {
    			initPlayerState(players[i]);
    		}
    
    	updateAllBehaviours(null, SELECTED);
    
    }
    
    /************************************************************
     * Restarts the game.
     ************************************************************/
    function restartGame () {
        KEYBINDINGS_ENABLED = false;
    
    
    	clearTimeout(timeoutID); // No error if undefined or no longer valid
    	timeoutID = autoForfeitTimeoutID = undefined;
    	stopCardAnimations();
    
    	/* enable table opacity */
    	tableOpacity = 1;
    	$gameTable.css({opacity:1});
        $gamePlayerClothingArea.show();
        $gamePlayerCardArea.show();
    
    	/* trigger screen refreshes */
    	updateSelectionVisuals();
    	updateAllGameVisuals();
        selectTitleCandy();
    
        forceTableVisibility(true);
    
    	/* there is only one call to this right now */
    	$epilogueSelectionModal.hide();
    	$gameScreen.hide();
    	$epilogueScreen.hide();
    
    	$titleScreen.show();
    }
    
    /**********************************************************************
     *****                    Interaction Functions                   *****
     **********************************************************************/
    
    /************************************************************
     * The player clicked the credits button. Shows the credits modal.
     ************************************************************/
    function showCreditModal () {
        $creditModal.modal('show');
    }
    
    /************************************************************
     * The player clicked the version button. Shows the version modal.
     ************************************************************/
    function showVersionModal () {
        $versionModal.modal('show');
    }
    
    /************************************************************
     * The player clicked on a table opacity button.
     ************************************************************/
    function toggleTableVisibility () {
    	if (tableOpacity > 0) {
    		$gameTable.fadeOut();
    		tableOpacity = 0;
    	} else {
    		$gameTable.fadeIn();
    		tableOpacity = 1;
    	}
    }
    
    function forceTableVisibility(state) {
        if (!state) {
    		$gameTable.fadeOut();
    		tableOpacity = 0;
    	} else {
    		$gameTable.fadeIn();
    		tableOpacity = 1;
    	}
    }
    
    /**********************************************************************
     *****                     Utility Functions                      *****
     **********************************************************************/
    
    /************************************************************
     * Returns a random number in a range.
     ************************************************************/
    function getRandomNumber (min, max) {
    	return Math.floor(Math.random() * (max - min) + min);
    }
    
    
    /************************************************************
     * Changes the first letter in a string to upper case.
     ************************************************************/
    String.prototype.initCap = function() {
    	return this.substr(0, 1).toUpperCase() + this.substr(1);
    }
    
    
    /**********************************************************************
     * Returns the width of the visible screen in pixels.
     **/
    
    FarawayVision's avatar
    FarawayVision committed
    function getScreenWidth ()
    
    {
    	/* fetch all game screens */
    	var screens = document.getElementsByClassName('screen');
    
    	/* figure out which screen is visible */
    
    FarawayVision's avatar
    FarawayVision committed
    	for (var i = 0; i < screens.length; i++)
    
    FarawayVision's avatar
    FarawayVision committed
    		if (screens[i].offsetWidth > 0)
    
            {
    			/* this screen is currently visible */
    			return screens[i].offsetWidth;
    		}
    	}
    }
    
    /**********************************************************************
     * Automatically adjusts the size of all font based on screen width.
     **/
    
    FarawayVision's avatar
    FarawayVision committed
    function autoResizeFont ()
    
    {
    	/* resize font */
    	var screenWidth = getScreenWidth();
    	document.body.style.fontSize = (14*(screenWidth/1000))+'px';
    
    
    	if (backgroundImage && backgroundImage.height && backgroundImage.width) {
    		var w = window.innerWidth, h = window.innerHeight;
    		if (h > (3/4) * w) {
    			h = (3/4) * w;
    		} else {
    
    			w = 4 * h / 3;
    
    		var ar = backgroundImage.width / backgroundImage.height;
    		if (ar > 4/3) {
    			var scale = Math.sqrt(16/9 / ar);
    			$("body").css("background-size", "auto " + Math.round(scale * h) + "px");
    
    			var scale = Math.sqrt(ar);
    			$("body").css("background-size", Math.round(scale * w) + "px auto");
    
    	/* set up future resizing */
    	window.onresize = autoResizeFont;
    
    
    /* Get the number of players loaded, including the human player.*/
    function countLoadedOpponents() {
        return players.reduce(function (a, v) { return a + (v ? 1 : 0); }, 0);
    }