Detecting Game Played on a Mobile Phone

I have dynamic game panes, map and hyperlinks in my recent game (http://textadventures.co.uk/games/view/m4a7-u7kyukbtvhaqtxq-g/sir-loin-and-the-coming-of-age-too) and want to switch all of these off by default, except use hyperlinks when the game is played on a mobile phone. I found a two year old discussion on the Forum on this topic. Is it the most up to date position or is there an easier way to detect mobile phone use now?
https://textadventures.co.uk/forum/quest/topic/zr75mcavwk_ezxmpdwgg9w/detecting-device-or-screen-size


K.V.

There is a JS platformvariable as of Quest 5.8. It will return the string "mobile" in the mobile player.

It seems like Pixie set up a Quest function to easily check that, but I am away from my computer and can't check that. I will be home soon, though.


K.V.

Here we are:

// This will set game.questplatform to "desktop", "webplayer", or "mobile".
// I put it in the User Interface Initialisation script, so it runs when a new game OR a saved game is loaded.
JS.whereAmI ()
// Give Quest a few seconds to receive the info from JS
SetTimeout (3) {
  // For debugging:
  msg ("PLATFORM: " + game.questplatform)
}

https://github.com/textadventures/quest/blob/f9605fd47fe9a2e9ff00f211df3ee97179c3ac89/docs/js/whereami.md


NOTE TO PIXIE

The "Javascript functions" link is bad.
image


Thanks K.V., that code worked in a test game but when I used it in the game itself there is a problem. I've been experimenting but I'm perhaps missing something important in my understanding of how this works. Can I execute this code more than once in a game? Is it okay if I define the 'questplatform' attribute in advance (in this case giving it 'mobile' as a default)? Is the timed wait necessary if there is a continue wait before the attribute is examined? In the following test code there is a command 'detect' that uses your code above, allowing a 5 second wait; why does it not work?
http://play2.textadventures.co.uk/Play.aspx?id=wvjqmlnedu6g_brvapmb6a

Detecting the platform is useful for me as my first two games can't be played to completion on a phone and it would be good to provide that warning initially.


Can I execute this code more than once in a game?

Why would you want to?
If you run it in UI initialisation then it is run every time the game starts or is loaded. A game shouldn't change from mobile to desktop without being saved and reloaded, so the value of game.questplatform will always be correct.

Is it okay if I define the 'questplatform' attribute in advance?

Again, why would you want to? If the code runs as soon as the game starts, you should never need to access that variable before it is set.

Is the timed wait necessary if there is a continue wait before the attribute is examined?

The timed wait is just for debugging. You would never do that in a real game.

In a real game you would set a script attribute game.changedquestplatform, so that the message is displayed as soon as we hear back from the javascript component. I'm guessing that a timer is used when debugging so that it shows something even if the javascript fails somehow.


K.V.

In a real game you would set a script attribute game.changedquestplatform, so that the message is displayed as soon as we hear back from the javascript component.

Doh!

Honestly, I was only using the timer because I never thought of this.

...seems like a no-brainer in retrospect.


K.V.

Here's an updated script:

// Setting game.questplatform to "undefined" because the attribute does not exist before play begins (although I believe it should to avoid scripting errors).
game.questplatform = "undefined"
// Just for debugging purposes:
msg ("{i:DEBUGGING MESSAGE:} PLATFORM: " + game.questplatform)
// Now, following mrangel's advice, I'm setting up a change script:
game.changedquestplatform => {
  // This is where the settings should be changed for specific platforms.
  msg ("{i:DEBUGGING MESSAGE:} PLATFORM: " + game.questplatform)
}
// This will set game.questplatform to "desktop", "webplayer", or "mobile".
// I put this all in the User Interface Initialisation script so it runs when a new game OR a saved game is loaded.
JS.whereAmI ()

Can I execute this code more than once in a game?

It won't hurt anything, but (as mrangel pointed out) I put it in the UI Init script, so it runs when the game loads whether it's a new game or a saved game.

The only thing I could imagine that a player might do during play is to change their mobile browser to desktop view during play (or vice versa), which would not trigger a change unless you had something set up in Javascript that kept track of something... or something. I don't know what could be done for that. As a player, if changing from mobile view to desktop view during play messed something up, I'd blame myself instead of the game.

Is it okay if I define the 'questplatform' attribute in advance (in this case giving it 'mobile' as a default)?

You could set it to anything, but it would change within seconds.

I assume you want to do this to avoid scripting errors? Depending on what you're scripting for the mobile player, this may or may not negatively effect anything. If you're tinkering with display settings, though, the player might see things quickly change back and forth if they weren't playing on mobile when the script ran.

Is the timed wait necessary if there is a continue wait before the attribute is examined?

It usually takes Javascript 2 to 4 seconds to pass something to Quest. That's why I used it.

Without a delay in the first script I posted, the attribute would not exist yet when the second line of code ran. So, I added the timer to avoid errors. From now on, I will be using change scripts. I learned something today! Yippee!!!

Thanks to everyone involved (especially mrangel)! Whoo-hoo!


DavyB, post your script (unless you've fixed at this point).

I'd guess you are manually setting it to "mobile" somewhere in that script. It behaves strangely when I tinker with it through the JS console online. I can run whereAmI() from the console, then enter DETECT, and it will display "PLATFORM: webplayer" as it should. The first time it prints "mobile", though. And I am not on mobile.


The only thing I could imagine that a player might do during play is to change their mobile browser to desktop view during play (or vice versa),

Every browser I've tried it on, this causes a page refresh so the game would be loaded anyway.

The platform variable in Quest's javascript is set based on whether playerweb.js or playermobile.js is loaded. So if there's some browser which allows mobile/desktop mode switching without a reload, WhereAmI would still report what kind of browser it was when the game started, and Quest's layout would not change at all.


Sorry for any confusion! My questions were to help me to try to work out why the code was not working. I've now updated the test program with K.V.'s code and unfortunately it's still not recognising that it is working online. Below is the full script. It shows the patches that have built up in handling dynamic panes, links and map and other tweaks. Hope you can make sense of it.

Thanks!

<!--Saved by Quest 5.8.6836.13983-->
<asl version="580">
  <include ref="English.aslx" />
  <include ref="Core.aslx" />
  <template name="UnresolvedObject">Sorry, I can't see that.</template>
  <game name="Test">
    <gameid>0e1e8167-0c69-4d58-900a-90d86f8a9452</gameid>
    <version>1.0</version>
    <firstpublished>2018</firstpublished>
    <attr name="panes_on" type="boolean">false</attr>
    <gridmap type="boolean">false</gridmap>
    <showpanes type="boolean">false</showpanes>
    <attr name="autodescription_description" type="int">0</attr>
    <attr name="autodescription_youcango" type="int">0</attr>
    <feature_advancedscripts />
    <autodescription type="boolean">false</autodescription>
    <attr name="links_on" type="boolean">false</attr>
    <attr name="map_on" type="boolean">false</attr>
    <questplatform>mobile</questplatform>
    <roomenter type="script">
    </roomenter>
    <start type="script"><![CDATA[
      JS.ShowGrid (0)
      msg ("IN THE BEGINNING...<br/><br/>A long, long time ago, somewhere between the dark ages and the light ages...in some sort of dawny age... Come-Here-A-Lot Castle sat proudly on English soil, somewhere near Bognor Regis. That was where King Arnold reigned, aided by his loyal and self-righteous Knights of the Brown Table.")
      wait {
        msg ("<br/>Many people longed to become Knights of the Brown Table. One such person was Loin, a small orphan boy brought to the castle by the kindly Quartermaster, Stan.<br/><br/>As Loin lies sleeping in the hay in the Royal Stable, he dreams that one day he will wield the sword of the Knights and serve his King.")
        wait {
          msg ("<br/>And today may just be the day...or perhaps tomorrow?")
          wait {
            msg ("<br/><center><b>Day One: Helping the King</b><br/><br/>[Type {command:options:<i>options</i>} for initial set up and {command:help:<i>help</i>} for general guidance]</center><br/><br/>You awake in the stable, the place where you have lived since moving into Come-Here-A-Lot Castle.<br/>")
          }
        }
      }
    ]]></start>
    <inituserinterface type="script"><![CDATA[
      // Fix for nested wait operations
      JS.eval ("$(window).on('keydown',function(e){ if(_waitMode){endWait(); e.preventDefault(); e.stopPropagation();}}); $(window).on('click',function(e){if(_waitMode){endWait(); e.preventDefault(); e.stopPropagation(); }});")
      // Stop resizing of map
      JS.eval ("gridApi.zoomIn = function(){};")
      // Stop movement of map
      JS.eval ("paper.getTool().off('mousedrag');")
      // Set map as necessary
      if (game.map_on) {
        JS.ShowGrid (300)
        // Centre map
        Grid_DrawPlayerInRoom (game.pov.parent)
      }
      else {
        JS.ShowGrid (0)
      }
      // Set game panes as necessary
      if (game.panes_on) {
        JS.uiShow ("#gamePanes")
      }
      else {
        JS.uiHide ("#gamePanes")
      }
      if (game.map_on) {
        Grid_DrawPlayerInRoom (game.pov.parent)
      }
      // Setting game.questplatform to "undefined" because the attribute does not exist before play begins (although I believe it should to avoid scripting errors).
      game.questplatform = "undefined"
      // Just for debugging purposes:
      msg ("{i:DEBUGGING MESSAGE:} PLATFORM: " + game.questplatform)
      // Now, following mrangel's advice, I'm setting up a change script:
      game.changedquestplatform => {
        // This is where the settings should be changed for specific platforms.
        if (game.questplatform = "mobile") {
          game.links_on = true
        }
        msg ("{i:DEBUGGING MESSAGE:} PLATFORM: " + game.questplatform)
      }
      // This will set game.questplatform to "desktop", "webplayer", or "mobile".
      // I put this all in the User Interface Initialisation script so it runs when a new game OR a saved game is loaded.
      JS.whereAmI ()
      // Set hyperlinks as necessary
      SetHyperlinkStatus (game.links_on)
      // The next bit controlling hyperlinks could be pasted into an included JS file, but works just as well in JS.eval()
      JS.eval ("if (typeof(linksEnabled)=='undefined'){var linksEnabled = true;} function updateCommandLinks(data) {     $('.commandlink').each(function (index, e) {         var $e = $(e);         if (!$(e).data('deactivated')) {             var elementid = $e.data('elementid');             var available = $.inArray(elementid, data) > -1 || elementid.length == 0;             if (available) {                 if (linksEnabled) {$e.removeClass('disabled');}             } else {                 $e.addClass('disabled');             }         }     });$('.cmdlink').each(function(){	if (!linksEnabled) {		$(this).addClass('disabled');	}});};")
    ]]></inituserinterface>
  </game>
  <object name="room">
    <inherit name="editor_room" />
    <alias>almost empty room</alias>
    <description type="script">
    </description>
    <object name="player">
      <inherit name="editor_object" />
      <inherit name="male" />
    </object>
  </object>
  <command name="platform">
    <pattern>platform</pattern>
    <script>
      msg (game.questplatform)
    </script>
  </command>
  <command name="detect">
    <pattern>detect</pattern>
    <script>
      // This will set game.questplatform to "desktop", "webplayer", or "mobile".
      // I put it in the User Interface Initialisation script, so it runs when a new game OR a saved game is loaded.
      JS.whereAmI ()
      // Give Quest a few seconds to receive the info from JS
      SetTimeout (5) {
        // For debugging:
        msg ("PLATFORM: " + game.questplatform)
      }
    </script>
  </command>
  <function name="SetHyperlinkStatus" parameters="setting">
    if (setting) {
      bool = "true"
    }
    else {
      bool = "false"
    }
    JS.eval ("var linksEnabled = "+bool+";")
    if (bool = "false") {
      game.suppresshyperlinks = true
      game.links_on = false
      JS.eval ("$('.cmdlink,.commandlink').each(function(){$(this).addClass('disabled');});")
    }
    else {
      game.suppresshyperlinks = false
      game.links_on = true
    }
  </function>
</asl>

I would expect that to say "DEBUGGING MESSAGE: PLATFORM: undefined" followed by one of "DEBUGGING MESSAGE: PLATFORM: webplayer" (or desktop, or mobile).

Note that this code:

 // Set hyperlinks as necessary
 SetHyperlinkStatus (game.links_on)

will be run on initialisation. The changedquestplatform script will run after this. So if you want this to run differently because the game is on mobile, you need to either move this line into a game.changedlinks_on script (so it runs whenever game.links_on is changed), or move it into the game.changedquestplatform script.

The following JS.eval call refers to a variable which is set by the SetHyperlinkStatus script. So you need to move this into the changedquestplatform script as well. Either that or have it check if the variable platform (which already exists in Javascript) is set to "mobile".

The reason you need to wait for a response is that calling JS.eval (or any other JS. function) adds a javascript command to a queue to be sent to the browser. These commands are then all sent at once, as soon as all Quest scripts have finished running. So after your UI initialisation script has finished, and the start script has finished, and the room enter script for the first room has finished, all these commands are sent to the javascript engine.

The javascript whereAmI function is simple. It looks at the javascript variable platform, and sends it back to Quest. This is then placed in a queue again, if Quest is already doing something. If some other javascript has sent an ASLEvent back, or if a timer has triggered before the browser's response got back (which could happen in the web version), they will be queued up because only one Quest function can run at a time; and anything that these functions send via msg, or via JS., will also be put in a queue so it's all sent at once the next time Quest doesn't have anything to do.

So the response to any JS. command will only happen after everything else in the script that called it has finished.


K.V.

Yep. What mrangel said.

That's why I have the timer before the debugging message, but neither the start script nor the UI Init script seem to be the culprits here.

When testing in the desktop, it works as expected.

image


It does not work online though.

image


It is stuck on "undefined", even when using the DETECT command, but I can open the HTML console and run whereAmI(), and it returns "webplayer".

image


Hrmm...


http://play2.textadventures.co.uk/Play.aspx?id=_khdf--kyuyrtiaw5zoiia


K.V.

I don't know what the problem is online.

I made a bare bones game and tried all sorts of different approaches. If I call JS.whereAmI() from the UI Init script online, it works. If I try to call JS.whereAmI() at any other time during play online, it does not set game.questplatform. But I can call whereAmI() from the console, and it will set game.questplatform.

So it behaves pretty much the same way in a bare bones test game as it does in DavyB's example.

This is beyond my comprehension. In fact, it's possible that mrangel has already explained why this doesn't work online. (Maybe something to do with a JS queue gone bananas?)

Sorry. I tried for quite a while. It's beyond me.


I need to take a closer look at this.
I took a quick look over the source, but can't find the relevant bits (as usual). I always seem to end up unable to find the source for the playerUI object.

Is it that JS.whereAmI() fails on web but JS.eval("whereAmI()") works? How about JS.whereAmI("foo")?


Perhaps hold back a little mrangel until I can clarify the problem? ...though seeing the source of the function would help. From experimenting this morning, I notice that JS.whereAmI() only changes game.questplatform if there is a difference between the recorded platform value and the actual platform.


K.V.

Is it that JS.whereAmI() fails on web but JS.eval("whereAmI()") works?

When calling JS.whereAmI() from the UI Init script online, it works. (So does JS.eval("whereAmI();").)

If you try to call it during play online, it does not work when called from Quest. But it does work when called directly from the HTML console.

In the desktop, it always works.

How about JS.whereAmI("foo")?

Tried this, too. It made no difference.


From experimenting this morning, I notice that JS.whereAmI() only changes game.questplatform if there is a difference between the recorded platform value and the actual platform.

So it only changes it when necessary.


Here is the bare bones example game. It does not call JS.whereAmI() from the UI Init script in this example.

There are 2 commands: PLATFORM and DETECT (following DavyB's model).

PLATFORM simply prints the value of game.questplatform if it exists.

DETECT simply calls JS.whereAmI(), and this only works correctly in the desktop player.

<!--Saved by Quest 5.8.6794.35054-->
<asl version="580">
  <include ref="English.aslx" />
  <include ref="Core.aslx" />
  <game name="PLATFORM Test 2">
    <gameid>13db3f83-ddf3-4ae5-a6f7-79ce8e09716b</gameid>
    <version>1.0</version>
    <firstpublished>2018</firstpublished>
    <menufont>Georgia, serif</menufont>
    <feature_advancedscripts />
    <inituserinterface type="script"><![CDATA[
      game.changedquestplatform => {
        msg (game.questplatform)
      }
      // JS.whereAmI ()
    ]]></inituserinterface>
  </game>
  <object name="room">
    <inherit name="editor_room" />
    <isroom />
    <object name="player">
      <inherit name="editor_object" />
      <inherit name="editor_player" />
    </object>
  </object>
  <command name="detect">
    <pattern>detect</pattern>
    <script>
      // This will set game.questplatform to "desktop", "webplayer", or "mobile".
      JS.whereAmI ()
    </script>
  </command>
  <command name="platform">
    <pattern>platform</pattern>
    <script>
      if (HasAttribute(game, "questplatform")) {
        msg (game.questplatform)
      }
      else {
        msg ("game.questplatform is not defined.")
      }
    </script>
  </command>
</asl>

NOTE:

If I uncomment the JS.whereAmI() line in the UI Init script, that works correctly online, but the DETECT command never works online.

Here is the link to play with this online:
https://play2.textadventures.co.uk/Play.aspx?id=editor/05dcae04-b0bb-4e19-895a-2e22a03348bf%2fPLATFORM+Test+2.aslx

Note that you can open the HTML console and call whereAmI() and it will work correctly.


K.V.

PS

I even tried JS.eval ("whereAmI();addTextAndScroll('Done.');")

The message prints, but game.questplatform was not set when playing online.


I don't know what is under the bonnet here, but the problem seems to be connected with Quest moving on from inituseinterface before whereAmI has finished and the completion then being mishandled later on. Specifically, in the game itself, which I've updated (http://textadventures.co.uk/games/view/m4a7-u7kyukbtvhaqtxq-g/sir-loin-and-the-coming-of-age-too) you will notice that the start script has a series of blocks of text separated by three continues. On playing, the first wait is skipped as if triggered by some internal event. Could that be the completion of whereAmI being mishandled?

After the second wait, I've put in:

if (game.questplatform = "unset") {
      JS.whereAmI ()
    }

The execution of this code then causes the final wait to be triggered though we do end up with questplatform finally being set correctly (type platform to see it).


K.V.

Hrmm...

There seem to be multiple things going on.

One is that JS.whereAmI() calls ASLEvent("WhereAmI", platform) from Javascript. This causes a turn to finish, unless we use...

Egad! What is that new function to suppress the turn script?

UPDATE: It is SuppressTurnscripts. (Duh!)


Anyway, most of the same issues exist in the bare bones game. We can use it to isolate the problem with JS.whereAmI() before moving on to the other issues in the larger example game.


K.V.

Try this, DavyB, but be ready to undo the change (just in case):

    if (game.questplatform = "unset") {
      SuppressTurnscripts
      JS.whereAmI ()
    }

That might fix the issue with that first Continue link getting skipped over.


K.V.

the problem seems to be connected with Quest moving on from inituseinterface before whereAmI has finished and the completion then being mishandled later on.

I don't think so. JS.whereAmI() does not work outside of the UI Init script for me when playing online, even if the UI Init script is empty. (Meaning it fails even when JS.whereAmI() isn't called when play begins.)


I've kept experimenting and found that the problem seems to be connected with the 'wait' operations. The version of the game that I have put up has just one call to JS.whereAmI(), and that is in the script to be executed before entry to the first location of the game. It isn't quite working properly as the links only appear after the description of the first location. I tried to put in a 'wait' to give time for JS.whereAmI() to work but then found that it failed completely! Feels like progress but does it help you?


I think there was something about wait working oddly, so that it might be messed up by an ASLEvent (as the Quest engine is in a different state waiting for a wait response than it is when it's expecting any other response).

In this case, the only thing I can suggest is making the script that includes the wait not run until after the ASLEvent has fired back. For example, have a temporary pre-starting room with the player object in. Then have your changedquestplatform script check if the player is still there, and if so move them to the actual starting room, firing that room's onenter scripts which can display the game intro and use wait as much as they want.

Either that or modify the ASLEvent function on the javascript side (which would be a pain because web and desktop have completely separate versions of that function) and make it queue responses until the server is ready to accept a normal response.


Thanks for the suggestion mrangel. I think it makes sense for the initial game description that includes the nested wait operations to appear in the start script. Until the wait issue is resolved, it means putting the "whereAmI ()" call after that and making adjustments as appropriate when the event fires. The only slight problem is that when I switch hyperlinks on after the event fires, they don't appear until the player has entered a command. It will, however, be fine until the wait problem has been sorted out.

If I use a dummy starting room it shows up on the game map, and is a bit confusing.


You could always postpone the start script until the javascript has done its thing.

Something like, In UI Initialisation script:

JS.whereAmI()
if (HasScript (game, "start")) {
  game.postponed_start_script = game.start
  game.start = null
}
else {
  // If we're loading a saved game, the variable is already set.
  // In that case, we set it back to "test in progress" so that
  // the changed script will still be called, in case there's any state that
  // needs setting up on the JS side
  game.questplatform = "test in progress"
}
game.changedquestplatform => {
  switch (this.questplatform) {
    case ("webplayer") {
      // insert relevant code here
    }
    case ("desktop") {
      // insert relevant code here
    }
    case ("mobile") {
      // insert relevant code here
    }
  }
  // Once everything is set up, we run the start script if necessary
  if (HasScript (this, "postponed_start_script")) {
    do (this, "postponed_start_script")
    // then delete our copy of the start script so it doesn't run again on reload.
    this.postponed_start_script = null
  }
  // stop this script from running when we change it back to "test in progress" if a saved game is loaded
  this.changedquestplatform = null
}

Thanks mrangel, I'll try this out but it feels like I'm getting into deeper water! Looking at the problem another way, as 'questplatform' is only needed when a game is loaded could it be set before any code of the game is executed? ...and remove 'JS.whereAmI()'?


That's why I put it in the UI initialisation script, and postponed the 'start' script until after the JS call has returned.

The javascript runs in the browser, so it can just look at the browser and see if it's mobile or not. But you can't set a Quest variable from that without giving the browser a fraction of a second to report that information back to Quest. And it's that callback that's causing the problem here - Quest doesn't expect any messages from the browser during a wait.

Even if you could remove the call to JS.whereAmI(), you'd have exactly the same problem. The javascript has the information you need, the Quest backend doesn't, and you can't pass information between them while a script is running.

When can JS safely report that data back to the Quest backend?

  1. Before the wait - To do this, you just need to postpone the start script until after the wait is finished; which my script above does.
  2. During the wait - Breaks on the web version due to the really messy way wait is implemented. Fixing this is not realistic.
  3. After the wait - You found some problems with this method. You could work around some of them by having your script call FinishTurn when it's done. I still think it's neater to force the JS magic to run first, though.

Sorry mrangel, I don't know enough about the code involved but I was hoping that the platform could be detected before any game code is executed, and the result inserted into the platform game attribute, ready immediately when the inituserinterface code is executed? This would avoid any patch and make it available to all existing games...but as I say, that is just hand waving!

In the code you provided to delay the 'start' script, it is now running after the script for the first location?


I wouldn’t even bother making Quest work on a mobile, it barely works online with a desktop.

So you’re left with a simple check of whether it’s online or offline. Which creates a Boolean between the two.

So, if it doesn’t work online that’s the default and otherwise it checks for offline which we know works.


Actually that’s a load of nonsense XD
Never mind.


Log in to post a reply.

Support

Forums