From cac182d9c2d47eb17454824b717b846226ee404b Mon Sep 17 00:00:00 2001 From: Sebastian Mobo <stmobo@gmail.com> Date: Thu, 10 May 2018 19:28:27 -0500 Subject: [PATCH] Add separate worker for preloading, also add character preloading on selection --- index.html | 5 +- js/preloader_worker.js | 26 ++++++ js/spniBehaviour.js | 112 +++++++++++++--------- js/spniGame.js | 205 ++++++++++++++++++++++++----------------- js/spniSelect.js | 1 + js/svc_worker.js | 49 ++++++++++ offline_host.py | 10 +- service_worker.js | 106 ++++++++++----------- 8 files changed, 328 insertions(+), 186 deletions(-) create mode 100644 js/preloader_worker.js diff --git a/index.html b/index.html index e914a42515b..7e97b054ccd 100644 --- a/index.html +++ b/index.html @@ -6,9 +6,6 @@ <link rel="shortcut icon" type="image/x-icon" href="img/icon.ico" /> <title>Strip Poker Night at the Inventory</title> - <!-- Load service worker ASAP --> - <script src="js/svc_worker.js" type="text/javascript"></script> - <!-- Stylesheets --> <link rel="stylesheet" type="text/css" href="css/bootstrap.min.css"> <link rel="stylesheet" type="text/css" href="css/bootstrap-theme.min.css"> @@ -1606,5 +1603,7 @@ <script src="js/spniEpilogue.js"></script> <script src="js/spniGallery.js"></script> +<script src="js/svc_worker.js" type="text/javascript"></script> + </body> </html> diff --git a/js/preloader_worker.js b/js/preloader_worker.js new file mode 100644 index 00000000000..45561d89f33 --- /dev/null +++ b/js/preloader_worker.js @@ -0,0 +1,26 @@ +/* Preloader Worker script. + * Loads files in the background and caches them. + * Send it lists of URLs to preload. + */ + +const CACHE_NAME = 'SPNATI-v1'; // this isn't shared between this context and the ServiceWorker context... + +onmessage = function (event) { + console.log("[PL] Preloading "+event.data.length.toString()+" URLs..."); + caches.open(CACHE_NAME).then( + function (cache) { + return Promise.all(event.data.map(async function (url) { + var resp = await fetch("../"+url, { headers: { 'X-Worker-Initiated': 'true' } }); + + return cache.put(url, resp); + })); + } + ).then( + function () { + console.log("[PL] Preload successful."); + }, + function (err) { + console.log("[PL] Preload failed: "+err.toString()); + } + ); +} diff --git a/js/spniBehaviour.js b/js/spniBehaviour.js index 565f5c2cd95..e5e78a48df2 100644 --- a/js/spniBehaviour.js +++ b/js/spniBehaviour.js @@ -7,7 +7,7 @@ /********************************************************************** ***** State Object Specification ***** **********************************************************************/ - + /************************************************************ * Stores information on AI state. ************************************************************/ @@ -17,14 +17,14 @@ function createNewState (dialogue, image, direction, silent, marker) { direction:direction, silent:silent, marker:marker}; - + return newStateObject; } /********************************************************************** ***** All Dialogue Tags ***** **********************************************************************/ - + var NAME = "~name~"; var PROPER_CLOTHING = "~Clothing~"; var LOWERCASE_CLOTHING = "~clothing~"; @@ -39,7 +39,7 @@ var SWAP_CARDS = "swap_cards"; var BAD_HAND = "bad_hand"; var OKAY_HAND = "okay_hand"; var GOOD_HAND = "good_hand"; - + var PLAYER_MUST_STRIP_WINNING = "must_strip_winning"; var PLAYER_MUST_STRIP_NORMAL = "must_strip_normal"; var PLAYER_MUST_STRIP_LOSING = "must_strip_losing"; @@ -102,19 +102,21 @@ var FEMALE_FINISHED_MASTURBATING = "female_finished_masturbating"; var GAME_OVER_VICTORY = "game_over_victory"; var GAME_OVER_DEFEAT = "game_over_defeat"; - + /********************************************************************** ***** Behaviour Parsing Functions ***** **********************************************************************/ /************************************************************ - * Loads and parses the start of the behaviour XML file of the + * Loads and parses the start of the behaviour XML file of the * given opponent source folder. * - * The callFunction parameter must be a function capable of + * The callFunction parameter must be a function capable of * receiving a new player object and a slot number. ************************************************************/ function loadBehaviour (folder, callFunction, slot) { + console.log("Loading behavior from "+folder); + $.ajax({ type: "GET", url: folder + "behaviour.xml", @@ -127,7 +129,7 @@ function loadBehaviour (folder, callFunction, slot) { var size = $(xml).find('size').text(); var timer = $(xml).find('timer').text(); var intelligence = $(xml).find('intelligence'); - + var tags = $(xml).find('tags'); var tagsArray = []; if (typeof tags !== typeof undefined && tags !== false) { @@ -135,35 +137,53 @@ function loadBehaviour (folder, callFunction, slot) { tagsArray.push($(this).text()); }); } - + var newPlayer = createNewPlayer(folder, first, last, label, gender, size, intelligence, Number(timer), tagsArray, xml); - + + + if(sw_is_active()) { + /* Ask SW to preload images for us. + * First get all unique image files for this character. + */ + var player_imgs = []; + + $(xml).find("state").each(function () { + var img = folder+$(this).attr('img'); + if($.inArray(img, player_imgs) === -1) { + player_imgs.push(img); + } + }); + + console.log("Preloading "+player_imgs.length.toString()+" image files from "+folder+" ..."); + request_url_caching(player_imgs); + } + callFunction(newPlayer, slot); } }); } /************************************************************ - * Parses and loads the wardrobe section of an opponent's XML + * Parses and loads the wardrobe section of an opponent's XML * file. ************************************************************/ function loadOpponentWardrobe (player) { /* grab the relevant XML file, assuming its already been loaded */ var xml = player.xml; player.clothing = []; - + /* find and grab the wardrobe tag */ $wardrobe = $(xml).find('wardrobe'); - + /* find and create all of their clothing */ $wardrobe.find('clothing').each(function () { var properName = $(this).attr('proper-name'); var lowercase = $(this).attr('lowercase'); var type = $(this).attr('type'); var position = $(this).attr('position'); - + var newClothing = createNewClothing(properName, lowercase, type, position, null, 0, 0); - + player.clothing.push(newClothing); }); } @@ -173,35 +193,35 @@ function loadOpponentWardrobe (player) { ************************************************************/ function parseDialogue (caseObject, replace, content) { var states = []; - + caseObject.find('state').each(function () { var image = $(this).attr('img'); var dialogue = $(this).html(); var direction = $(this).attr('direction'); var silent = $(this).attr('silent'); var marker = $(this).attr('marker'); - + if (replace && content) { for (var i = 0; i < replace.length; i++) { dialogue = dialogue.split(replace[i]).join(content[i]); } } - + if (silent !== null && typeof silent !== typeof undefined) { silent = true; } else { silent = false; } - + states.push(createNewState(dialogue, image, direction, silent, marker)); }); - + return states; } /************************************************************ - * Updates the behaviour of the given player based on the + * Updates the behaviour of the given player based on the * provided tag. ************************************************************/ function updateBehaviour (player, tag, replace, content, opp) { @@ -211,28 +231,28 @@ function updateBehaviour (player, tag, replace, content, opp) { /* their is restricted to this only */ //tag = players[player].forfeit[0]; //} - + if (!players[player]) { return; } - + /* get the AI stage */ var stageNum = players[player].stage; - + /* try to find the stage */ var stage = null; $(players[player].xml).find('behaviour').find('stage').each(function () { if (Number($(this).attr('id')) == stageNum) { stage = $(this); - } + } }); - + /* quick check to see if the stage exists */ if (!stage) { console.log("Error: couldn't find stage for player "+player+" on stage number "+stageNum+" for tag "+tag); return; } - + /* try to find the tag */ var states = []; $(stage).find('case').each(function () { @@ -254,7 +274,7 @@ function updateBehaviour (player, tag, replace, content, opp) { // look for the best match var bestMatch = []; var bestMatchPriority = -1; - + for (var i = 0; i < states.length; i++) { var target = states[i].attr("target"); @@ -294,12 +314,12 @@ function updateBehaviour (player, tag, replace, content, opp) { var totalPriority = 0; // this is used to determine which of the states that // doesn't fail any conditions should be used - - + + /////////////////////////////////////////////////////////////////////// // go through different conditions required until one of them fails // if none of them fail, then this state is considered for use with a certain priority - + // target (priority = 300) if (opp !== null && typeof target !== typeof undefined && target !== false) { target = target; @@ -312,7 +332,7 @@ function updateBehaviour (player, tag, replace, content, opp) { continue; // failed "target" requirement } } - + // filter (priority = 150) if (opp !== null && typeof filter !== typeof undefined && filter !== false) { // check against tags @@ -327,7 +347,7 @@ function updateBehaviour (player, tag, replace, content, opp) { continue; // failed "filter" requirement } } - + // targetStage (priority = 80) if (opp !== null && typeof targetStage !== typeof undefined && targetStage !== false) { var targetPieces = targetStage.split("-"); @@ -382,7 +402,7 @@ function updateBehaviour (player, tag, replace, content, opp) { } } } - + // oppHand (priority = 30) if (opp !== null && typeof oppHand !== typeof undefined && oppHand !== false) { var failedOppHandReq = false; @@ -416,7 +436,7 @@ function updateBehaviour (player, tag, replace, content, opp) { continue; // failed "targetTimeInStage" requirement } } - + // hasHand (priority = 20) if (typeof hasHand !== typeof undefined && hasHand !== false) { if (handStrengthToString(hands[player].strength) === hasHand) { @@ -426,10 +446,10 @@ function updateBehaviour (player, tag, replace, content, opp) { continue; // failed "hasHand" requirement } } - + // alsoPlaying, alsoPlayingStage, alsoPlayingTimeInStage, alsoPlayingHand (priority = 100, 40, 15, 5) if (typeof alsoPlaying !== typeof undefined && alsoPlaying !== false) { - + var foundEm = false; var j = 0; for (j = 0; j < players.length && foundEm === false; j++) { @@ -443,7 +463,7 @@ function updateBehaviour (player, tag, replace, content, opp) { } } } - + if (foundEm === false) { continue; // failed "alsoPlaying" requirement @@ -555,7 +575,7 @@ function updateBehaviour (player, tag, replace, content, opp) { continue; // failed "timeInStage" requirement } } - + // totalMales (priority = 5) if (typeof totalMales !== typeof undefined && totalMales !== false) { var count = 0; @@ -576,7 +596,7 @@ function updateBehaviour (player, tag, replace, content, opp) { continue; // failed "totalMales" requirement } } - + // totalFemales (priority = 5) if (typeof totalFemales !== typeof undefined && totalFemales !== false) { var count = 0; @@ -695,10 +715,10 @@ function updateBehaviour (player, tag, replace, content, opp) { if (typeof customPriority !== typeof undefined) { totalPriority = parseInt(customPriority, 10); //priority override } - + // Finished going through - if a state has still survived up to this point, // it's then determined if it's the highest priority so far - + if (totalPriority > bestMatchPriority) { console.log("New best match with " + totalPriority + " priority."); @@ -709,9 +729,9 @@ function updateBehaviour (player, tag, replace, content, opp) { { bestMatch.push(states[i]); } - + } - + if (bestMatch.length > 0) { bestMatch = bestMatch[Math.floor(Math.random() * bestMatch.length)] players[player].current = 0; @@ -722,7 +742,7 @@ function updateBehaviour (player, tag, replace, content, opp) { } /************************************************************ - * Updates the behaviour of all players except the given player + * Updates the behaviour of all players except the given player * based on the provided tag. ************************************************************/ function updateAllBehaviours (player, tag, replace, content, opp) { @@ -731,4 +751,4 @@ function updateAllBehaviours (player, tag, replace, content, opp) { updateBehaviour(i, tag, replace, content, opp); } } -} \ No newline at end of file +} diff --git a/js/spniGame.js b/js/spniGame.js index a9088eb6268..af1df46e798 100644 --- a/js/spniGame.js +++ b/js/spniGame.js @@ -1,16 +1,16 @@ /******************************************************************************** - This file contains the variables and functions that form the main game screen of + This file contains the variables and functions that form the main game screen of the game. The main game progression (dealing, exchanging, revealing, stripping) and everything to do with displaying the main game screen. ********************************************************************************/ - + /********************************************************************** ***** Game Screen UI Elements ***** **********************************************************************/ /* game banner */ $gameBanner = $("#game-banner"); - + /* main UI elements */ $gameBubbles = [$("#game-bubble-1"), $("#game-bubble-2"), @@ -62,10 +62,10 @@ $debugButtons = [$("#debug-button-0"), $("#debug-button-2"), $("#debug-button-3"), $("#debug-button-4")]; - + /* restart modal */ $restartModal = $("#restart-modal"); - + /********************************************************************** ***** Game Screen Variables ***** **********************************************************************/ @@ -79,13 +79,13 @@ var CARD_SUGGEST = false; var AUTO_FORFEIT = false; var AUTO_FADE = true; var KEYBINDINGS_ENABLED = false; -var DEBUG = false; - +var DEBUG = true; + /* colours */ var currentColour = "#63AAE7"; /* indicates current turn */ var clearColour = "#FFFFFF"; /* indicates neutral */ var loserColour = "#DD4444"; /* indicates loser of a round */ - + /* game state */ var currentTurn = 0; var currentRound = -1; @@ -97,18 +97,43 @@ var actualMainButtonState = false; var endWaitDisplay = 0; var showDebug = false; var chosenDebug = -1; - + +/* Array of images to cache in the background */ +const static_images = [ + 'img/all.png', + 'img/any.png', + 'img/bisexual.jpg', + 'img/blankcard.jpg', + 'img/enter.png', + 'img/female.png', + 'img/female_large.png', + 'img/female_medium.png', + 'img/female_small.png', + 'img/gallery.svg', + 'img/icon.ico', + 'img/icon.jpg', + 'img/male.png', + 'img/male_large.png', + 'img/male_medium.png', + 'img/male_small.png', + 'img/reddit.png', + 'img/title.png', + 'img/unknown_s.jpg', + 'img/unknown.jpg', + 'img/unknown.svg', +]; + /********************************************************************** ***** Start Up Functions ***** **********************************************************************/ - + /************************************************************ - * Loads all of the content required to display the title + * Loads all of the content required to display the title * screen. ************************************************************/ function loadGameScreen () { $gameScreen.show(); - + /* reset all of the player's states */ for (var i = 1; i < players.length; i++) { if (players[i] != null) { @@ -128,28 +153,42 @@ function loadGameScreen () { $gamePlayerCountdown.hide(); chosenDebug = -1; updateDebugState(showDebug); - + + + if(sw_is_active()) { + set_sw_debug(DEBUG); + /* autogenerate lists of cards to save */ + var cards = []; + for(suit of [SPADES, HEARTS, CLUBS, DIAMONDS]) { // filename prefixes + cards.push('img/'+suit+'.jpg'); + for(let i=1;i<=13;i++) { + cards.push('img/'+suit+i.toString()+'.jpg'); + } + } + request_url_caching(cards.concat(static_images)); + } + /* set up the visuals */ updateAllGameVisuals(); - + /* set up the poker library */ setupPoker(); - + /* disable player cards */ for (var i = 0; i < $cardButtons.length; i++) { $cardButtons[i].attr('disabled', true); } - + /* enable and set up the main button */ $mainButton.html("Deal"); $mainButton.attr("disabled", false); actualMainButtonState = false; - + /* late settings */ KEYBINDINGS_ENABLED = true; document.addEventListener('keyup', game_keyUp, false); } - + /********************************************************************** ***** Display Functions ***** **********************************************************************/ @@ -176,14 +215,14 @@ function updateGameVisual (player) { if (players[player].state[players[player].current].direction) { $gameBubbles[player-1].removeClass(); $gameBubbles[player-1].addClass("bordered dialogue-bubble dialogue-"+chosenState.direction); - } + } /* update image */ $gameImages[player-1].attr('src', players[player].folder + chosenState.image); /* update label */ $gameLabels[player].html(players[player].label); - + /* check silence */ if (chosenState.silent) { $gameBubbles[player-1].hide(); @@ -208,11 +247,11 @@ function updateGameVisual (player) { $gameDialogues[player-1].html(""); $gameAdvanceButtons[player-1].css({opacity : 0}); $gameBubbles[player-1].hide(); - + $gameImages[player-1].attr('src', BLANK_PLAYER_IMAGE); } } - + /************************************************************ * Updates all of the main visuals on the main game screen. ************************************************************/ @@ -222,7 +261,7 @@ function updateAllGameVisuals () { updateGameVisual (i); } } - + /************************************************************ * Updates the visuals of the player clothing cells. ************************************************************/ @@ -234,7 +273,7 @@ function displayHumanPlayerClothing () { clothingImages.push(players[HUMAN_PLAYER].clothing[i].image); } } - + /* display the remaining clothing items */ clothingImages.reverse(); $gameClothingLabel.html("Your Clothing"); @@ -255,10 +294,10 @@ function displayHumanPlayerClothing () { /************************************************************ * Determines what the AI's action will be. ************************************************************/ -function makeAIDecision () { +function makeAIDecision () { /* determine the AI's decision */ determineAIAction(currentTurn); - + /* dull the cards they are trading in */ for (var i = 0; i < hands[currentTurn].tradeIns.length; i++) { if (hands[currentTurn].tradeIns[i]) { @@ -273,11 +312,11 @@ function makeAIDecision () { swap++; } } - + /* update a few hardcoded visuals */ updateBehaviour(currentTurn, SWAP_CARDS, [CARDS, PLAYER_NAME], [swap, players[HUMAN_PLAYER].label], null); updateGameVisual(currentTurn); - + /* wait and implement AI action */ window.setTimeout(implementAIAction, GAME_DELAY); } @@ -287,10 +326,10 @@ function makeAIDecision () { ************************************************************/ function implementAIAction () { exchangeCards(currentTurn); - + /* refresh the hand */ hideHand(currentTurn); - + /* update behaviour */ determineHand(currentTurn); if (hands[currentTurn].strength == HIGH_CARD) { @@ -303,7 +342,7 @@ function implementAIAction () { updateBehaviour(currentTurn, GOOD_HAND, [PLAYER_NAME], [players[HUMAN_PLAYER].label], null); updateGameVisual(currentTurn); } - + /* wait and then advance the turn */ window.setTimeout(advanceTurn, GAME_DELAY); } @@ -316,7 +355,7 @@ function advanceTurn () { if (currentTurn >= players.length) { currentTurn = 0; } - + if (players[currentTurn]) { /* highlight the player who's turn it is */ for (var i = 0; i < players.length; i++) { @@ -337,7 +376,7 @@ function advanceTurn () { return; } } - + /* allow them to take their turn */ if (currentTurn == 0) { /* human player's turn */ @@ -362,9 +401,9 @@ function advanceTurn () { makeAIDecision(); } } - + /************************************************************ - * Deals cards to each player and resets all of the relevant + * Deals cards to each player and resets all of the relevant * information. ************************************************************/ function startDealPhase () { @@ -388,7 +427,7 @@ function startDealPhase () { if (HUMAN_PLAYER == i) { $gamePlayerCardArea.hide(); $gamePlayerClothingArea.hide(); - } + } else { $gameOpponentAreas[i-1].hide(); } @@ -396,9 +435,9 @@ function startDealPhase () { players[i].timeInStage++; } } - + /* IMPLEMENT STACKING/RANDOMIZED TRIGGERS HERE SO THAT AIs CAN COMMENT ON PLAYER "ACTIONS" */ - + /* clear the labels */ for (var i = 0; i < players.length; i++) { $gameLabels[i].css({"background-color" : clearColour}); @@ -418,7 +457,7 @@ function checkDealLock () { inGame++; } } - + /* check the deal lock */ if (dealLock < inGame * 5) { window.setTimeout(checkDealLock, 100); @@ -426,14 +465,14 @@ function checkDealLock () { /*set up main button*/ if (players[HUMAN_PLAYER].out && players[HUMAN_PLAYER].finished) continueDealPhase() - else if (players[HUMAN_PLAYER].out) { + else if (players[HUMAN_PLAYER].out) { $mainButton.html("Next"); $mainButton.attr('disabled', false); actualMainButtonState = true } else { - continueDealPhase() + continueDealPhase() } - + } } @@ -447,7 +486,7 @@ function continueDealPhase () { $gameAdvanceButtons[i-1].css({opacity : 0}); $gameBubbles[i-1].hide(); } - + /* set visual state */ if (!players[HUMAN_PLAYER].out) { showHand(HUMAN_PLAYER); @@ -455,16 +494,16 @@ function continueDealPhase () { for (var i = 1; i < players.length; i++) { hideHand(i); } - + /* enable player cards */ for (var i = 0; i < $cardButtons.length; i++) { $cardButtons[i].attr('disabled', false); } - + /* suggest cards to swap, if enabled */ if (CARD_SUGGEST && !players[HUMAN_PLAYER].out) { determineAIAction(HUMAN_PLAYER); - + /* dull the cards they are trading in */ for (var i = 0; i < hands[HUMAN_PLAYER].tradeIns.length; i++) { if (hands[HUMAN_PLAYER].tradeIns[i]) { @@ -472,7 +511,7 @@ function continueDealPhase () { } } } - + /* allow each of the AIs to take their turns */ currentTurn = 0; advanceTurn(); @@ -487,7 +526,7 @@ function completeExchangePhase () { /* exchange the player's chosen cards */ exchangeCards(HUMAN_PLAYER); showHand(HUMAN_PLAYER); - + /* disable player cards */ for (var i = 0; i < $cardButtons.length; i++) { $cardButtons[i].attr('disabled', true); @@ -507,29 +546,29 @@ function completeRevealPhase () { showHand(i); } } - + /* figure out who has the lowest hand */ previousLoser = recentLoser; recentLoser = determineLowestHand(); - + if (chosenDebug !== -1 && DEBUG) { recentLoser = chosenDebug; } - + console.log("Player "+recentLoser+" is the loser."); - + /* look for the unlikely case of an absolute tie */ if (recentLoser == -1) { console.log("Fuck... there was an absolute tie"); /* inform the player */ - + /* hide the dialogue bubbles */ for (var i = 1; i < players.length; i++) { $gameDialogues[i-1].html(""); $gameAdvanceButtons[i-1].css({opacity : 0}); $gameBubbles[i-1].hide(); } - + /* reset the round */ $mainButton.html("Deal"); $mainButton.attr('disabled', false); @@ -553,12 +592,12 @@ function completeRevealPhase () { players[previousLoser].consecutiveLosses = 0; //reset last loser players[recentLoser].consecutiveLosses = 1; } - - + + /* update behaviour */ var clothes = playerMustStrip (recentLoser); updateAllGameVisuals(); - + /* highlight the loser */ for (var i = 0; i < players.length; i++) { if (recentLoser == i) { @@ -567,7 +606,7 @@ function completeRevealPhase () { $gameLabels[i].css({"background-color" : clearColour}); } } - + /* set up the main button */ if (recentLoser != HUMAN_PLAYER && clothes > 0) { $mainButton.html("Continue"); @@ -583,7 +622,7 @@ function completeRevealPhase () { /************************************************************ * Processes everything required to complete the continue phase - * of a round. A very short phase in which a player removes an + * of a round. A very short phase in which a player removes an * article of clothing. ************************************************************/ function completeContinuePhase () { @@ -625,23 +664,23 @@ function endRound () { lastPlayer = i; } } - + /* if there is only one player left, end the game */ if (inGame <= 1) { console.log("The game has ended!"); $gameBanner.html("Game Over! "+players[lastPlayer].label+" won Strip Poker Night at the Inventory!"); gameOver = true; - + for (var i = 0; i < players.length; i++) { if (HUMAN_PLAYER == i) { $gamePlayerCardArea.hide(); $gamePlayerClothingArea.hide(); - } + } else { $gameOpponentAreas[i-1].hide(); } } - + handleGameOver(); } else { $mainButton.html("Deal"); @@ -665,11 +704,11 @@ function handleGameOver() { left++; } } - + /* determine true end */ if (left == 0) { /* true end */ - + //identify winner var winner = -1; for (var i = 0; i < players.length; i++){ @@ -682,9 +721,9 @@ function handleGameOver() { var tag = (i == winner) ? GAME_OVER_VICTORY : GAME_OVER_DEFEAT; updateBehaviour(i, tag, [NAME, PLAYER_NAME], [players[winner].label, players[HUMAN_PLAYER].label], players[winner]); } - + updateAllGameVisuals(); - + $mainButton.html("Ending?"); $mainButton.attr('disabled', false); actualMainButtonState = false; @@ -695,13 +734,13 @@ function handleGameOver() { for (var i = 0; i < endWaitDisplay; i++) { dots += "."; } - + /* someone is still forfeiting */ $mainButton.html("Wait" + dots); $mainButton.attr('disabled', false); } } - + /********************************************************************** ***** Interaction Functions ***** **********************************************************************/ @@ -711,37 +750,37 @@ function handleGameOver() { ************************************************************/ function selectCard (card) { hands[HUMAN_PLAYER].tradeIns[card] = !hands[HUMAN_PLAYER].tradeIns[card]; - + if (hands[HUMAN_PLAYER].tradeIns[card]) { dullCard(HUMAN_PLAYER, card); } else { fillCard(HUMAN_PLAYER, card); } } - + /************************************************************ * The player clicked the advance dialogue button on the main * game screen. ************************************************************/ function advanceGameDialogue (slot) { players[slot].current++; - + /* update dialogue */ $gameDialogues[slot-1].html(players[slot].state[players[slot].current].dialogue); - + /* determine if the advance dialogue button should be shown */ if (players[slot].state.length > players[slot].current+1) { $gameAdvanceButtons[slot-1].css({opacity : 1}); } else { $gameAdvanceButtons[slot-1].css({opacity : 0}); } - + /* direct the dialogue bubble */ if (players[slot].state[players[slot].current].direction) { $gameBubbles[slot-1].removeClass(); $gameBubbles[slot-1].addClass("bordered dialogue-bubble dialogue-"+players[slot].state[players[slot].current].direction); - } - + } + /* update image */ $gameImages[slot-1].attr('src', players[slot].folder + players[slot].state[players[slot].current].image); } @@ -751,14 +790,14 @@ function advanceGameDialogue (slot) { ************************************************************/ function advanceGame () { var context = $mainButton.html(); - + /* disable the button to prevent double clicking */ $mainButton.attr('disabled', true); actualMainButtonState = true; - + /* lower the timers of everyone who is forfeiting */ context = tickForfeitTimers(context); - + /* handle the game */ if (context == "Deal") { /* dealing the cards */ @@ -838,7 +877,7 @@ function closeRestartModal() { /************************************************************ * A keybound handler. ************************************************************/ -function game_keyUp(e) +function game_keyUp(e) { console.log(e); if (KEYBINDINGS_ENABLED) { @@ -871,7 +910,7 @@ function game_keyUp(e) } -function selectDebug(player) +function selectDebug(player) { if (chosenDebug === player) { chosenDebug = -1; @@ -883,7 +922,7 @@ function selectDebug(player) } -function updateDebugState(show) +function updateDebugState(show) { if (!show) { for (var i = 0; i < $debugButtons.length; i++) { @@ -897,7 +936,7 @@ function updateDebugState(show) $debugButtons[i].removeClass("active"); } } - + if (chosenDebug !== -1) { $debugButtons[chosenDebug].addClass("active"); } diff --git a/js/spniSelect.js b/js/spniSelect.js index b086ea4d419..494c9401856 100644 --- a/js/spniSelect.js +++ b/js/spniSelect.js @@ -843,6 +843,7 @@ function backToSelect () { * select screen. ************************************************************/ function advanceSelectScreen () { + console.log("Starting game..."); advanceToNextScreen($selectScreen); } diff --git a/js/svc_worker.js b/js/svc_worker.js index ac8eb618165..868923ef4be 100644 --- a/js/svc_worker.js +++ b/js/svc_worker.js @@ -1,3 +1,5 @@ +var preloader_worker = null; + if ('serviceWorker' in navigator) { window.addEventListener('load', function() { navigator.serviceWorker.register('/service_worker.js').then(function(registration) { @@ -7,7 +9,27 @@ if ('serviceWorker' in navigator) { // registration failed console.log('ServiceWorker registration failed: ', err); }); + + if(sw_is_active()) { + set_sw_debug(true); + } }); + + if(window.Worker) { + console.log("Starting Preloader Worker..."); + preloader_worker = new Worker('js/preloader_worker.js'); + } +} + +function sw_is_active() { + /* check to see if we can load SWs in the first place + * then make sure that the Controller object is not null/undefined/etc. + */ + return ('serviceWorker' in navigator) && (navigator.serviceWorker.controller != null); +} + +function preloader_active() { + return preloader_worker != null; } function send_msg_to_sw(msg) { @@ -27,3 +49,30 @@ function send_msg_to_sw(msg) { } ); } + +function request_url_caching(urls) { + if(preloader_active()) { + /* Preload resources in the background using the preloader worker if we can. */ + preloader_worker.postMessage(urls); + } else { + /* Otherwise fall back to having the ServiceWorker do the loading. */ + return send_msg_to_sw({ 'type': 'cache', 'urls': urls }).then(function (ok) { + if(!ok) { + console.error("Attempt to cache urls failed: "+urls.toString()) + } + + return ok; + }); + } +} + +/* Set debug mode true/false in the ServiceWorker */ +function set_sw_debug(debug_status) { + return send_msg_to_sw({ 'type': 'set-debug', 'debug': debug_status}).then(function (ok) { + if(!ok) { + console.error("Attempt to set ServiceWorker debug status failed.") + } + + return ok; + }); +} diff --git a/offline_host.py b/offline_host.py index d60c66aed0c..62ff2173413 100644 --- a/offline_host.py +++ b/offline_host.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python from bottle import get, post, request, response, redirect, route, run, static_file import os @@ -11,9 +11,11 @@ port=5000 def redir_index(): return redirect('/index.html') +# Bottle should do this automatically, but it doesn't for some reason ext_mimetypes = { '.png': 'image/png', '.jpg': 'image/jpeg', + '.svg': 'image/svg+xml', '.css': 'text/css', '.js': 'text/javascript', '.html': 'text/html', @@ -31,4 +33,8 @@ def statics(filename): return static_file(filename, root=os.getcwd(), mimetype=mimetype) -run(host=host, port=port, reloader=True) +try: + import cherrypy + run(server='cherrypy', host=host, port=port) +except ImportError: + run(host=host, port=port) diff --git a/service_worker.js b/service_worker.js index 011f4fe1742..0f77d03b330 100644 --- a/service_worker.js +++ b/service_worker.js @@ -1,41 +1,13 @@ /* NOTE: This file must be accessable from the root of the domain! */ const CACHE_NAME = 'SPNATI-v1'; -const CACHE_KEEPALIVE = 3600; // refetch cached content older than this many seconds, if online +const CACHE_KEEPALIVE = 600; // refetch cached content older than this many seconds, if online -/* this list holds images that should always be cached - * (player clothes, the logo, etc.) - * We autogenerate the lists to cache cards and backgrounds. - * +/* This list holds the URLs of resources that should always be cached * All URLs in this list and in `static_content` will be cached when the * ServiceWorker is installed (i.e. when SPNATI is loaded for the first time) + * This _will_ block load until all resources have been loaded. */ -const static_images = [ - 'img/all.png', - 'img/any.png', - 'img/bisexual.jpg', - 'img/blankcard.jpg', - 'img/enter.png', - 'img/female.png', - 'img/female_large.png', - 'img/female_medium.png', - 'img/female_small.png', - 'img/gallery.svg', - 'img/icon.ico', - 'img/icon.jpg', - 'img/male.png', - 'img/male_large.png', - 'img/male_medium.png', - 'img/male_small.png', - 'img/reddit.png', - 'img/title.png', - 'img/unknown_s.jpg', - 'img/unknown.jpg', - 'img/unknown.svg', -]; - -/* this list holds other things that should always be cached - * (JS, CSS, etc.) */ const static_content = [ 'js/bootstrap.min.js', 'js/jquery-1.11.3.min.js', @@ -67,12 +39,40 @@ const static_content = [ 'css/spni.css', ]; +/* When debugging is active, we always fetch and recache non-image data. */ +var debug_active = false; + self.addEventListener('fetch', function(event) { /* Don't bother caching non-GET requests */ if(event.request.method !== 'GET') { - return fetch(event.request); + return event.respondWith(fetch(event.request)); + } + + + if(event.request.headers.get('X-Worker-Initiated') != null) { + var alt_req = new Request(event.request); + alt_req.headers.delete("X-Worker-Initiated"); + + return event.respondWith(fetch(alt_req)); + } + + if(debug_active) { + let file_ext = event.request.url.split('.').pop(); + if(file_ext !== 'png' && file_ext !== 'jpg' && file_ext !== 'svg') { + return event.respondWith( + fetch(event.request).then( + async function (net_response) { + var cache = await caches.open(CACHE_NAME); + cache.put(event.request, net_response.clone()); + + return net_response; + } + ) + ); + } } + /* Look for content in the cache first. * If we don't find it, or if the cached version is too old, attempt to * retrieve it from the network, and cache the retrieved version. @@ -98,46 +98,48 @@ self.addEventListener('fetch', function(event) { cache.put(event.request, net_response.clone()); return net_response; } - ) + ).catch(function (err) { + console.error("Caught error when responding to request: "+err.toString()); + console.error(err.stack); + }) ); }); +/* Fires on install (i.e. when there's a new version of the SW or when first visiting the page) */ self.addEventListener('install', function(event) { event.waitUntil( caches.open(CACHE_NAME).then(function(cache) { - /* autogenerate lists of cards to save */ - var cards = []; - for(suit of ['clubs', 'diamo', 'heart', 'spade']) { // filename prefixes - cards.push('img/'+suit+'.jpg'); - for(let i=1;i<=13;i++) { - cards.push('img/'+suit+i.toString()+'.jpg'); - } - } - - var bgs = []; - for(let bgIdx=1;bgIdx<=23;bgIdx++) { - bgs.push('img/background'+bgIdx.toString()+'.png'); - } - /* Combine all lists of stuff to cache on install */ - return cache.addAll(static_content.concat(static_images, cards, bgs)); + return cache.addAll(static_content); + }).then(function () { + /* Make sure we're active immediately */ + return self.skipWaiting(); }) ); }); +self.addEventListener('activate', function (event) { + /* Force SW to become available to all clients-- this ensures serviceWorker.controller is available to everyone */ + return event.waitUntil(self.clients.claim()); +}) + self.addEventListener('message', function(event) { var msg = event.data; - /* NOTE: I'm not sure if we'll ever need to handle multiple types of messages, - * but I'll play it safe here... */ if (msg.type === 'cache') { event.waitUntil( caches.open(CACHE_NAME).then( (cache) => cache.addAll(msg.urls) ).then( - (value) => ev.ports[0].postMessage(true), - (error) => ev.ports[0].postMessage(false) + (value) => event.ports[0].postMessage(true), + (error) => event.ports[0].postMessage(false) ) ); + } else if(msg.type === 'set-debug') { + debug_active = msg.debug; + + if(debug_active) { + console.log("[SW] Debugging enabled -- bypassing cache for non-image files"); + } } }); -- GitLab