ShowMenu function not found - bug?

Blindtextdev
27 Jul 2018, 22:53

Hey all,

have a look at the following code:
ShowMenu("What's your race?", game.races, false) {
game.race = result
}
game.races contains three entries, all String.
When I run this code (web editor), the following error occurs:
Error running script: Function not found: "ShowMenu"
Did I make a mistake somewhere, or is this a bug?


mrangel
28 Jul 2018, 07:45

In your other posts, you seemed to be working on a gamebook.

ShowMenu is one of many core functions that only exist in text adventure mode.
If you want to use it in a gamebook, you'll have to include it yourself.

You could try including the library CoreFunctions.aslx; I'm not sure if that would cause any other problems. Or you could copy the menu-related functions into your game directly. I think these functions should be all you need:

  <function name="ShowMenu" parameters="caption, options, allowCancel, callback">
    <![CDATA[
    outputsection = StartNewOutputSection()
    msg (caption)
    count = 0
    game.menuoptionskeys = NewStringList()
    foreach (option, options) {
      count = count + 1
      if (TypeOf(options) = "stringdictionary") {
        optionText = StringDictionaryItem(options, option)
        optiontag = option
        style = GetCurrentLinkTextFormat()
        list add (game.menuoptionskeys, option)
      }
      else if (TypeOf(option) = "string") {
        optionText = option
        optiontag = option
        style = GetCurrentLinkTextFormat()
        list add (game.menuoptionskeys, option)
      }
      else if (TypeOf(option) = "object") {
        optionText = GetDisplayAlias(option)
        optiontag = option.name
        colour = ""
        if (HasString(option, "linkcolour") and GetUIOption("UseGameColours") = "true") {
          colour = option.linkcolour
        }
        else {
          colour = GetLinkTextColour()
        }
        style = GetCurrentTextFormat(colour)
        list add (game.menuoptionskeys, option.name)
      }
      else {
        error ("ShowMenu cannot handle a " + TypeOf(option))
      }
      msg (count + ": <a class=\"cmdlink\" style=\"" + style + "\" onclick=\"ASLEvent('ShowMenuResponse','" + EscapeQuotes(optiontag) + "')\">" + optionText + "</a>")
    }
    EndOutputSection (outputsection)
    game.menuoptions = options
    game.menuallowcancel = allowCancel
    game.menucallback = callback
    game.menuoutputsection = outputsection
  ]]>
  </function>
  
  <function name="ShowMenuResponse" parameters="option">
    if (game.menucallback = null) {
      error ("Unexpected menu response")
    }
    else {
      parameters = NewStringDictionary()
      dictionary add (parameters, "result", UnescapeQuotes(option))
      script = game.menucallback
      ClearMenu
      invoke (script, parameters)
    }
  </function>
  
  <function name="EscapeQuotes" parameters="s" type="string">
    s = Replace(s, "\"", "@@@doublequote@@@")
    s = Replace(s, "\'", "@@@singlequote@@@")
    return (s)
  </function>
  
  <function name="UnescapeQuotes" parameters="s" type="string">
    s = Replace(s, "@@@doublequote@@@", "\"")
    s = Replace(s, "@@@singlequote@@@", "\'")
    return (s)
  </function>

  <function name="ClearMenu">
    HideOutputSection(game.menuoutputsection)
    game.menuoutputsection = null
    game.menuoptions = null
    game.menucallback = null
  </function>

Blindtextdev
28 Jul 2018, 08:05

nods That's the issue. Thanks for clarifying - next time I'll go look for the function and include it manually.

Although, if a function isn't available, it shouldn't be possible to set it in the editor in the first place.


mrangel
28 Jul 2018, 08:53

Although, if a function isn't available, it shouldn't be possible to set it in the editor in the first place.

Yeah, that could be fixed. Possibly it would be worth either moving ShowMenu from CoreEditorScriptsOutput tab to one that isn't included in gamebook mode; or including the function.


Blindtextdev
28 Jul 2018, 17:32

Including the function would be better.

Next episode. I have implemented it now (couldn't use copy and paste as the code view in the web version would protest then). See the code below:

Function name: ShowMenu
Return type: none
Parameters: caption, options, allowCancel, callback
Script:
outputsection = StartNewOutputSection()
msg (caption)
count = 0
game.menuoptionskeys = NewStringList()
foreach (option, options) {
count = count + 1
if (TypeOf(options) = "stringdictionary") {
optionText = StringDictionaryItem(options, option)
optiontag = option
style = GetCurrentLinkTextFormat()
list add (game.menuoptionskeys, option)
}
else if (TypeOf(options) = "string") {
optionText = option
optiontag = option
style = GetCurrentLinkTextFormat()
list add (game.menuoptionskeys, option)
}
else if (TypeOf(options) = "object") {
optionText = GetDisplayAlias(option)
optiontag = option.name
colour = ""
if (HasString("linkcolour") and GetUIOption("UseGameColours") = "true" ) {
colour = option.linkcolour
}
else {
colour = GetLinkTextColour()
}
style = GetCurrentLinkTextFormat(colour)
list add (game.menuoptionskeys, option.name)
}
else {
error ("ShowMenu cannot handle a " + TypeOf(option))
}
msg (msg(count + ": <a class="cmdlink" style="" + style + "" onclick="ASLEvent('ShowMenuResponse','" + EscapeQuotes(optiontag) + "')">" + optionText + ""))
}
EndOutputSection (outputsection)
game.menuoptions = options
game.menuallowcancel = allowCancel
game.menucallback = callback
game.menuoutputsection = outputsection

Function name: ShowMenuResponse
Return type: none
Parameters: option
Script:
if (game.menucallback = null) {
error ("Unexpected menu response")
}
else {
parameters = NewStringDictionary()
dictionary add (parameters, ""result"", UnescapeQutes(option))
script = game.menucallback
ClearMenu
invoke (script, parameters)
}

Function name: EscapeQuotes
Return type: string
Parameters: s
Script:
s = Replace(s, """, "@@@doublequote@@@")
s =
Replace(s, "'", "@@@singlequote@@@")
return (s)

The function UnescapeQuotes is the same, just the replacing works the other way round.

Next:
Function name: ClearMenu
No parameters, no return type.
Script:
HideOutputSection (game.menuoutputsection)
game.menuoutputsection = null
game.menuoptions = null
game.menucallback = null
So. On a page I added this code:
game.races = NewStringList()
list add(game.races, "Vampire")
list add(game.races, "Human")
ShowMenu("Which race are you?", game.races, false) {
game.race = result
}

Running this, I get confronted with an error: ShowMenu cannot handle a string.

Which is weird, because I included a way to handle strings in ShowMenu. Any ideas?


hegemonkhan
28 Jul 2018, 20:31

you can just manually create your own menu (more simplistic, well my coding as I'm still a beginner in programming, than in using the full built-in menu coding, lol):

// scripting example of using it:

create ("example_object")

example_object.example_stringlist_attribute = Split ("red;blue;yellow", ";")

menu_display_function (example_object.example_stringlist_attribute, "Color?")

// -------------

<function name="menu_display_function" parameters="menu_attribute_parameter, menu_prompt_string_parameter">
  numbering_integer_variable = 0
  msg (menu_prompt_string_parameter)
  foreach (item_variable, menu_attribute_parameter) {
    numbering_integer_variable = numbering_integer_variable + 1
    msg (numbering_integer_variable + ". " + item_variable)
  }
</function>

// output/display:

Color?

1. red
2. blue
3. yellow

you can adjust/add to it as you like, and you also got to of course craft scripting/code (another Function or whatever) for handling it (getting user input and acting on it)

this post was just a really quick example of manually creating your own menu display


Blindtextdev
28 Jul 2018, 20:38

The display isn't the problem, but getting an interaction on it... I need a click event. And honestly don't know how to do that in aslx.


hegemonkhan
28 Jul 2018, 20:46

P.S.

you can do a lot of cool things, if you're creating the menu yourself, for one example of mine, that I like:

a random selection option (if you want to speed through it or can't make up your mind or if you just want a random result)

<function name="menu_function" parameters="menu_attribute_parameter, menu_prompt_string_parameter">
  menu_display_function (menu_attribute_parameter,menu_prompt_string_parameter)
  on ready {
    menu_handling_function (menu_attribute_parameter)
  }
</function>

<function name="menu_display_function" parameters="menu_attribute_parameter,menu_prompt_string_parameter">
  numbering_integer_variable = 0
  msg (menu_prompt_string_parameter)
  msg (numbering_integer_variable + ". random selection")
  foreach (item_variable, menu_attribute_parameter) {
    numbering_integer_variable = numbering_integer_variable + 1
    msg (numbering_integer_variable + ". " + item_variable)
  }
</function>

<function name="menu_handling_function" parameters="menu_attribute_parameter">
  <![CDATA[
    get input {
      if (IsInt (result)) {
        input_integer_variable = ToInt (result)
        input_index_number_integer_variable = input_integer_variable - 1
        list_count_integer_variable = ListCount (menu_attribute_parameter)
        last_index_number_integer_variable = list_count_integer_variable - 1
        if (input_integer_variable = 0) {
          player.example_string_attribute = StringListItem (menu_attribute_parameter, GetRandomInt (0, list_count_integer_variable ))
        } else if (input_integer_variable > 0 and input_index_number_integer_variable < list_count_integer_variable) {
          player.example_stringlist_attribute = StringListItem (menu_attribute_parameter, input_index_number_integer_variable)
        } else {
          // wrong input, try again (loop function / tail-recursion --- unfortunately, due to getting, having to wait for, user input, we can't do/use the 'while' Function, we got to do tail-recursion, in quest, sighs)
          menu_handling_function (menu_attribute_parameter)
        }
      } else {
        // wrong input, try again (loop function / tail-recursion --- unfortunately, due to getting, having to wait for, user input, we can't do/use the 'while' Function, we got to do tail-recursion, in quest, sighs)
        menu_handling_function (menu_attribute_parameter)
      }
    }
  ]]>
</function>

Blindtextdev
28 Jul 2018, 20:48

If I could figure out how to add a cliak event, I also just could use a list or whatever.


Blindtextdev
28 Jul 2018, 20:55

Looked through your functions. They're cool, ineed. But I think you use the GetInput function to get the selected value, right? That function displays a textfield. How do we keep the player from entering something in the textfield?


hegemonkhan
28 Jul 2018, 20:58

(ya, I'm the opposite of you, I like just having to press a number key for selections, lol --- I'l probably still also use the UI panes, hyperlinks, and etc mouse/click or typed-in input stuff too)

(working on a too-ambitious massive TES:morrowing/oblivion/skyrim scale of RPG... lol)

(still working on just my character creation of it... lol --- having a bit of trouble with creating an advanced/dynamic menu/input handling system... it's a bit beyond my ability, so I'm struggling a bit with it... sighs)

(character generation, from quickest to longest --- random: entirely automated random character customization, preset: pre-built characters, select: questions-answers that determines character customization, and custom: you do/select the full customization of a character, though each individual selection has the random selection option as well)

(trying to figure out how to make a really generalized/dynamic menu system, handling different inputs/data-types and their handling, passing in needed stuff to one super menu handling function, to not have code redundancy and make it as human-customizable-friendly-editable-scalable-encapsulated-compartmentalized as possible... but it's not easy for me to do this type of advanced design/structure level of code stuff, sighs)


click event? like a hyperlink? (such as with Objects: [ball] -> verbs/script-attributes: [kick], [throw])

easiest is via the text processor commands:

http://docs.textadventures.co.uk/quest/text_processor.html

create ("example_object")

create ("ball")

ball.kick => {
  msg ("You kick the ball")
}

ball.throw => {
  msg ("You throw the ball")
}

ball.displayverbs = NewStringlist ()
list add (ball.displayverbs, "kick")
list add (ball.displayverbs, "throw")

example_object.example_objectlist_attribute = NewObjectList ()
list add (example_object.example_objectlist_attribute, ball)

numbering_integer_variable = 0
foreach (object_variable, example_object.example_objectlist_attribute) {
  numbering_integer_variable = numbering_integer_variable + 1
  msg (numbering_integer_attribute + ". {object:" + object_variable.name + "}")
}

mrangel
28 Jul 2018, 21:03

In the ShowMenu function you have:
else if (TypeOf(options) = "string") {
that should be option, not options.

(Similarly, else if (TypeOf(options) = "object") { should be else if (TypeOf(option) = "object") {)

The first one, if (TypeOf(options) = "stringdictionary") {, is the only one that should be options.

(If options is a stringdictionary, it has special behaviour. Otherwise it goes through the items in the options list and checks each option to see if it is a string or an object. So you can have a list of options that contains some strings and some objects. You've got it checking if the list of options is a string, rather than each individual option)


hegemonkhan
28 Jul 2018, 21:04

not sure where it's found, but there's help on doing various UI stuff (like removing/hiding: toggling show/hide, of the input command bar at the bottom)...

(let me see if I can find it for you...)

https://textadventures.co.uk/forum/quest/topic/4853/showing-hiding-the-command-bar (not sure if this is the best link on it... but hopefully it's something... hopefully it's what you need...)

(there's a much more detailed guide somewhere on it and other various UI customization stuff (quest can use JS, so you can use html, css, and js, to do UI customization, if you know how to do/use this web-coding stuff)

http://docs.textadventures.co.uk/quest/ (scroll down to the 'UI customization' like links/section/category)

try searching around for more/other links, try pixie's github too:

https://github.com/ThePix/quest/wiki

hopefully, you can find what you need in one of these links... lol


hegemonkhan
28 Jul 2018, 21:21

P.S.

totally forgot to include this really useful link for more advanced coding stuff wth quest (sorry about that):

http://docs.textadventures.co.uk/quest/scripts/request.html

let's you access the various internal coding handling of stuff....


P.S.S.

also search for KV/RH and mrangel threads/posts as they've done a lot of work on/with the UI customization coding too

(I still don't really understand all of the UI coding yet... lazy... haven't gotten around to learning/studying it yet..)


Blindtextdev
28 Jul 2018, 21:28

Thank you! This will hopefully help. Let me fix my stuff...


Blindtextdev
28 Jul 2018, 22:03

OK, after fixing a typo and a duplicate msg, the menu now gets displayed. On click, it also gets removed, but I get the message that no variable 'result' exists. Shouldn't all this return a variable Called 'result'? The most likely cause is the ShowMenuResponse function, so here's the code again:
(Parameters = option, no return type)
if (game.menucallback = null) {
error ("Unexpected menu response")
}
else {
parameters = NewStringDictionary()
dictionary add (parameters, ""result"", UnescapeQuotes(option))
script = game.menucallback
ClearMenu
invoke (script, parameters)
}

Do you see my mistake? Because I can't find it...


mrangel
28 Jul 2018, 22:23

@HK:

      // wrong input, try again (loop function / tail-recursion --- unfortunately, due to getting, having to wait for, user input, we can't do/use the 'while' Function, we got to do tail-recursion, in quest, sighs)

That is not tail recursion. In fact, it isn't recursion at all.
Think about why tail recursion is considered a bad thing, and you'll see that it doesn't apply here.

@Blindtextdev:

    dictionary add (parameters, ""result"", UnescapeQuotes(option))

It looks like you have ""result"" instead of "result". Though I'd expect that to give an error SyntaxError: Unexpected token "result" rather than saying the variable doesn't exist.

See if fixing the doubled quotes helps.

Also, when pasting code you can use three backticks, like this:

```
code goes here
```

so that the code shows up properly indented and is easier to read.


hegemonkhan
28 Jul 2018, 23:45

@ mrangel:

I know what you're talking about, I thought that it's still called 'tail-recursion', even though it's not recursioning back to a prior scripting, as maybe it's still retaining the activation record of the scripting until it's finishing its looping of the scripting:

(err that wasn't well said, hopefully this is more clear, at what I thought was why it's still called/"considered" as 'tail-recursion', even though it's not really doing any nested scripting recursion)

function (1)
-> loop function (2)
-> (function (1) is still retained in memory) --- "so, it's possibly still considered as tail-recursion due to the activation record taking up memory"
-> (after function (2) finishes, then it goes back to function (1) to delete it from memory)

but if:

function (1)
-> loop function (2)
-> (function (1) is deleted from memory)
-> (run and complete function (2))
-> (function (2) is deleted from memory)

then, it's definately not even considered as tail-recursion, of course


that was my understanding anyways of 'tail-recursion' still being used even though you're not doing real recursion (returning back to the same sub-problem to finish it)

of course, this is actual recursion (be it actual recursion of: top/head recursion, middle recursion, or tail-recursion):

problem
-> sub-problem 1
->-> sub-problem 2
->->-> base case solution
->-> sub-problem 2 is finished up
->-> sub-problem 2 is deleted
-> sub-problem 1 is finished up
-> sub-problem 1 is deleted
problem is solved (finished up), and deleted


I'm still a little shakey/noob-ish with recursion... so I probably had this weird notion of mine, all wrong, not understanding what exactly is recursion properly

I'm just trying to explain why I had thought looping a function is still considered as tail-recursion, but I'm likely entirely wrong that it's to be still considered as tail-recursion. This was just my flawed "understanding" of recursion, that I've had all this time... sighs

(I wish I was better at math and smarter, so I'd be able to understand and use/do recursion fully and clearly, sighs. I have to really strain my brain when trying to do recursion design with coding/scripting, sighs)


Blindtextdev
29 Jul 2018, 08:27

It works! Thanks all!

Sorry for all the stupid questions... It's just that the code view for me as blind person looks pretty messy, it's hard to scroll all the way to a certain part of code, so hence I didn't notice the ""result"" thing, for example.

Also, thanks for the hint with the code - the backticks were displayed in English braille set, I'm used to German braille, so I didn't know which characters I had to place there. Hence, I used four spaces (I'm used to that from StackOverflow), but it obviously didn't work that well.

Oh well, now I know what to place there.


mrangel
29 Jul 2018, 09:33

@HK

maybe it's still retaining the activation record of the scripting until it's finishing its looping of the scripting:

It isn't. This is why variables before a ShowMenu call can't be used inside the result block. When you call ShowMenu, it displays a menu on the screen. It creates a script attribute (game.menucallback) containing the script you specify; and it sends a chunk of text to the player's browser, which has javascript 'onclick' functions to run that script attribute with different values of the result parameter.

If the menu displays itself again until it gets the right answer, that isn't recursion. Because the function is creating a link that, when clicked, will run the function again. It isn't actually calling itself. And the function will have terminated, and its variables disposed, before that link is actually displayed.

A function running itself is recursion.

The player clicking a link that runs a function, and once it's finished, the player clicking another link that runs the same function … that's just repetition.

(and now you've got me thinking … it would be fairly trivial to modify the ClearMenu function so that it keeps a backup copy of the script attribute and options list, that's cleared by a turnscript after one turn. Then you could make a RepeatQuestion() function that displays the last getinput/menu/ask/etc again, restoring the saved callback. That would make it look a bit more like a normal loop from the user's point of view)

@Blindtextdev

That must be pretty tough.
It says in the forum documentation that four spaces works, but I've only seen it work like that with ten spaces or more; and it seems to vary from post to post how many you need. But four spaces seems to work if it's after the > for quoted text.