Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Harlowe questions. Dataset random extraction, Array numeric extraction.

edited January 2016 in Help! with 2.0
Hi
I'm writing a simple quiz program. The person has to answer a fixed number of questions.
I have a bank of answer/question pairs set up as a database.

The answer is stored before the question because each answer is a single word or number and I don't know how to access a dataset with a multi-word 'key'

Question 1:
how do extract an answer/question pair from a dataset at random?

Question 2:
Arrays appear to be accessed by words instead of numbers, 1st, 2nd.
It's hard to see what value this method of extraction has
Is there any way to assess an array by number, so I can use some math?

So far the only way I can think of is to use a subarray, e.g.
(set: $Fruit to (a: "banana", "apple", "pear", "peach"))
(set: $Key to 3)
(subarray: $Fruit, $Key, $Key)
(set: $Key to 2)
(subarray: $Fruit, $Key, $Key)

Thank you

Comments

  • Harlowe's Manual briefly mentions using expressions to access array elements in the Array Data section:
    If you don't know the exact position to remove an item from, you can use an expression, in brackers, after it: $array's ($pos - 3).
    ... and you can do the same thing for datamaps.
    (set: $array to (array: "element 1", "element 2"))
    (set: $index to 2)
    
    ''Array element access using an expression''
    The first element is: (print: $array's (1))
    The second element is: (print: $array's ($index))
    
    (set: $map to (datamap: "key 1", "value 1", "key 2", "value 2"))
    (set: $key to "key 2")
    
    ''Datamap key/value access using an expression''
    The value for key 1 is: (print: $map's ("key 1"))
    The value for key 2 is: (print: $map's ($key))
    

    Based on the above, one answer to your first question is to use macros to generate a key like so.
    (set: $key to "key " + (text: (random: 1,2)))
    Random key/value pair: (print: $map's ($key))
    
    ... or if the list of questions were stored in an array you could shuffled them like so:
    (set: $listA to (array: 1,2,3,4,5,6))
    (set: $listB to (shuffled: ...$listA))
    List before shuffling: $listA
    List after shuffling: $listB
    
  • 1: I assume you mean a datamap, since it's not possible to extract individual elements from a dataset.
    One way would be to use the (datanames:) macro to get a list of all the answers and then use the (either:) macro to randomly pick an answer.
    (set: $qa to (datamap: "answer1", "question1", "answer2", "question2", "answer3", "question3"))
    (set: $answer to (either: ...(datanames: $qa)))
    (print: $qa's $answer)
    

    2: You can access array elements using numbers
    (print: $Fruit's 2)
    
    does the same as
    (print: $Fruit's 2nd)
    

    So this will work
    (set: $key to 3)
    (print: $Fruit's $key)
    
  • edited January 2016
    Because you generally don't want to ask the same question more than once I would suggest using (shuffled:) on the list of keys returned by (datanames:) instead of (either:) and then just read the shuffled keys starting with the first key and continue with the next one until you have the amount of keys you need.
  • Thanks guys, I will try this out
  • I'm getting much closer.
    The actual layout is a bit clunky, but largely working
    Still using the subarray to extract individual elements of an array as I can't find another method.

    The first passage (Quiz) does the setup and asks three questions.
    This part is working.
    {(Set: $Name1 to "The Princess")
    (set: $Quest to (datamap:
    "fruit", "Apples and pears are types of",
    "ice", "Frozen water is called",
    6, "The number after 5 is",
    7, "The number of days in a week is",
    "fork", "You eat your dinner with a knife and a",
    "school", "Children learn by going to a",
    4, "The number of legs that 2 ducks have is",
    "pony", "A small horse is called a",
    "moo", "A cow makes this sound"
    ))
    (set: $Answer to (shuffled: ...(datanames: $Quest )))
    (Set: $A1 to (subarray: $Answer, 1, 1))
    (Set: $A2 to (subarray: $Answer, 2, 2))
    (Set: $A3 to (subarray: $Answer, 3, 3))
    }
    To help $Name1 cross the bridge you must answer 3 questions correctly
    
    Type your answers in the boxes (in small letters)
    Then click the <b>"Finished answering"</b> button
    
    (Print: $Quest's $A1) ? []<Answer1|. <input type="text" name="Answer1" value="">
    (Print: $Quest's $A2) ? []<Answer2|. <input type="text" name="Answer2" value="">
    (Print: $Quest's $A3) ? []<Answer3|. <input type="text" name="Answer3" value="">
    {<button type="submit" onclick="customScripts.submitName('Answer1');
    customScripts.submitName('Answer2');
    customScripts.submitName('Answer3')">Finished answering</button>
    
    (live:100ms)[(set:$Answer1 = ?Answer1)(set:$Answer2 = ?Answer2)(set:$Answer3 = ?Answer3)]}
    
    [[Check the answers]]
    

    But the passage for checking the results (Check the answers)
    gives me an "Incorrect" on each attempt, even though they appear to match
    (set: $Score to 0)
    (if: $Answer1 is $A1)[(either: "Correct","Well done", "Good answer", "You are right", "That's right"). (set: $Score +=1)](else:)[Incorrect.  Your answer was $Answer1. ] (Print: $Quest's $A1) $A1
    
    (if: $Answer2 is $A2)[(either: "Correct","Well done", "Good answer", "You are right", "That's right"). (set: $Score +=1)](else:)[Incorrect. Your answer was $Answer2. ] (Print: $Quest's $A2) $A2
    
    (if: $Answer3 is $A3)[(either: "Correct","Well done", "Good answer", "You are right", "That's right"). (set: $Score +=1)](else:)[Incorrect.  Your answer was $Answer3. ] (Print: $Quest's $A3) $A3
    
    (if: $Score is 3)[Great! You got all 3 right,
    $Name1 can cross the bridge](else:)[Sorry, $Name1 can't cross the bridge this time
    (if: $Score > 0)[you got only $Score questions right](else:)[Oops! You didn't get any questions right.]
    
    [[Try again|Quiz]] ]
    
  • notes:
    a. If your example requires some Javascript for it to work then you should supply either a copy of that Javascript or a link to where the can be found.

    eg. your customScripts.submitName function.

    b. The (datamap:) macro documentation states that the names(keys) should be Strings, so they should not be other data types like numbers even if they appear to work.
    You can use the string version of a number, so your 6 should be "6", 4 should be "7" and 4 should be "4"

    c. HTML text input elements contain String (text) values, so any when comparing your $Answer1, $Answer2, $Answer3 variables to another variable that other variable also need to contain a String value for the comparison to be true.
    This was one of the issues causing your Check the answers passage to sometimes fail.

    eg. If the reader answered 4 to the first question then $Answer1 will contain "4", so the value within $A1 needs to be a String value of "4" (not a number 4) for the two variables to be equal.

    d. Accessing an array element or a datamap key using an expression.
    Still using the subarray to extract individual elements of an array as I can't find another method.
    I provided examples of how to use an expression to access both of these in my previous comment, they included both using a literal and a variable. The documentation states that the expression should be within parenthesis. eg. (expression)
    ''Array element access using an expression''
    The first element is: (print: $array's (1))
    The second element is: (print: $array's ($index))
    
    ''Datamap key/value access using an expression''
    The value for key 1 is: (print: $map's ("key 1"))
    The value for key 2 is: (print: $map's ($key))
    
    e. You can use a startup tagged passage to initialize (give default values to) your story's variables, this is a good idea especially for variables that don't change their values like your $Quest.

    The following are corrected versions of your examples, I have add new $Encouragements variable and formatted the code a little.

    1. Quiz passage
    {
    (set: $Name1 to "The Princess")
    (set: $Quest to (datamap:
    	"fruit", "Apples and pears are types of",
    	"ice", "Frozen water is called",
    	"6", "The number after 5 is",
    	"7", "The number of days in a week is",
    	"fork", "You eat your dinner with a knife and a",
    	"school", "Children learn by going to a",
    	"4", "The number of legs that 2 ducks have is",
    	"pony", "A small horse is called a",
    	"moo", "A cow makes this sound"
    ))
    (set: $Answer to (shuffled: ...(datanames: $Quest)))
    (set: $A1 to $Answer's (1))
    (set: $A2 to $Answer's (2))
    (set: $A3 to $Answer's (3))
    }
    To help $Name1 cross the bridge you must answer 3 questions correctly
    
    Type your answers in the boxes (in small letters)
    Then click the <b>"Finished answering"</b> button
    
    (print: $Quest's ($A1)) ? []<Answer1|. <input type="text" name="Answer1" value="">
    (print: $Quest's ($A2)) ? []<Answer2|. <input type="text" name="Answer2" value="">
    (print: $Quest's ($A3)) ? []<Answer3|. <input type="text" name="Answer3" value="">
    {<button type="submit" onclick="customScripts.submitName('Answer1');
    customScripts.submitName('Answer2');
    customScripts.submitName('Answer3')">Finished answering</button>
    
    (live:100ms)[
    	(set: $Answer1 to ?Answer1)
    	(set: $Answer2 to ?Answer2)
    	(set: $Answer3 to ?Answer3)
    ]
    }
    
    [[Check the answers]]
    
    2. Check the answers passage
    (set: $Score to 0)
    (set: $Encouragements to (array: "Correct", "Well done", "Good answer", "You are right", "That's right"))
    
    (if: $Answer1 is $A1)[(either: ...$Encouragements). (set: $Score += 1)](else:)[Incorrect.  Your answer was $Answer1. ] (Print: $Quest's ($A1)) $A1
    
    (if: $Answer2 is $A2)[(either: ...$Encouragements). (set: $Score += 1)](else:)[Incorrect.  Your answer was $Answer2. ] (Print: $Quest's ($A2)) $A2
    
    (if: $Answer3 is $A3)[(either: ...$Encouragements). (set: $Score += 1)](else:)[Incorrect.  Your answer was $Answer3. ] (Print: $Quest's ($A3)) $A3
    
    (if: $Score is 3)[Great! You got all 3 right,
    $Name1 can cross the bridge](else:)[Sorry, $Name1 can't cross the bridge this time
    (if: $Score > 0)[you got only $Score questions right](else:)[Oops! You didn't get any questions right.]
    
    [[Try again|Quiz]]]
    

    warning: There are a number of other potential issues with the code.

    1. You are assuming that the reader will enter their answers using only lowercase letters, so if they answer "FORK" or "Fork" (etc) to the question requiring "fork" they will be told they answered incorrectly when they didn't.
    You can fix this by converting the $Answer1, $Answer2, and $Answer3 variables to lowercase using the Javascript toLowerCase function before to compare them to the $A1, $A2, and $A3 variable.
    (set: $Answer1 to $Answer1.toLowerCase())
    

    2. You are also assuming that the reader will answer "4" instead of "four" to the questions requiring a numerical answer, which they may if they have previously answered questions requiring words for answers.
    The solution for this either requires you to: tell the readers to enter numerical answers using numbers, for you to check for both "4" and "four", or for you to expect people to complain that the story tells them they are incorrect when they are not.
  • edited January 2016
    Thank you greyelf
    I really appreciate your help
    Sorry about not including the java code. I have found several examples using it in the forum and thought it was “well known”.

    In my original code I had allowed for lower case and cap case, but had taken it out to simplify debugging.
    This module did not allow for spelt numbers, because it’s for two little kids aged 5 and 3, so they probably wouldn’t spell them. But I fully agree, it should be catered for. I know VBA (and used to program in many languages many years ago – even before ‘object oriented code’ existed), so I expect java code to be relatively easy to pick up.

    This is part of something bigger.

    I have been writing for years and recently I have been writing fairy tales for a friend’s kids (and stories for my friends). So CYOA seemed like a fun way to progress.
    A bit of research led me to Twine.

    Again, it was meant to be a simple Choose your own adventure. But once I got enTwined, I decided to broaden the scope.
    The story begins in a 12 room + 2 gardens + courtyard + blacksmith shop, palace.

    The player has to explore the palace to find a number of items (usual stuff), and must pick up a sidekick, and meet people in a certain order to get permission to go outside, then get the key, and only then can they 'pick up' the carriage, pony, picnic basket etc.

    The palace section is finished and working.

    When the player leaves the palace they enter the kingdom (5 x 4 grid), with an adventure/challenge in each. Some challenges can be overcome only if a certain item has been brought form the palace, others can be overcome without the item, but it’s harder.
    But if the have forgotten the carriage or pony they can't go anywhere, and if they have forgotten the map or food they can move only one square E, N or W before returning.

    In one grid square a bridge needs to be crossed, and the troll who guards it asks three questions to allow passage – and this is what the question module is needed for.
    Others require a word puzzle to be solved, and so on. And most squares can be entered more than once and from different directions.
    And sometimes the player has to return to the palace. E.g. when the pony loses a shoe it has to be taken back to the blacksmith.

    I have also written a few short fairy tales, which can be read from a link that appears as a reward for accomplishing something.
    It was useless without graphics, and I didn’t want the bother of being online and slow image hosting, so I have embedded them in base64 (currently over 70 images, with more to come) .
    There is 'treasure' to be found only if certain tasks have been completed, but I'm still working that one out.

    I have about ¼ of the kingdom finished and working.
    So while it’s meant to be for a couple of kids, I’m having as much fun writing it.
  • And here is the java code
    if (typeof window.customScripts == "undefined") {
    window.customScripts = {
    submitName: function(inputAnswer1) {
    var newAnswer1 = $("input[name='" + inputAnswer1 + "']")[0].value;
    $("tw-hook[name*='" + inputAnswer1 + "']").text(newAnswer1);
    console.log(inputAnswer1 + " changed.")
    }
    };
    };
    
    if (typeof window.customScripts == "undefined") {
    window.customScripts = {
    submitName: function(inputAnswer2) {
    var newAnswer2= $("input[name='" + inputAnswer2 + "']")[0].value;
    $("tw-hook[name*='" + inputAnswer2 + "']").text(newAnswer2);
    console.log(inputAnswer2 + " changed.")
    }
    };
    };
    
    if (typeof window.customScripts == "undefined") {
    window.customScripts = {
    submitName: function(inputAnswer3) {
    var newAnswer3= $("input[name='" + inputAnswer3 + "']")[0].value;
    $("tw-hook[name*='" + inputAnswer3 + "']").text(newAnswer3);
    console.log(inputAnswer3 + " changed.")
    }
    };
    };
    
    
    
    
    
    
    
    
  • It sounds like you and the young ones are having/will have fun.
    Jimowens wrote:
    Sorry about not including the java code ...
    ... so I expect java code to be relatively easy to pick up
    And here is the java code
    A small correction, you are writing Javascript code. Java is a totally unrelated programming language.

    In your Javascript code example you are defining three copies of the exact same function, the second and third definition are not needed and can be deleted.

    I personally use a slightly different version of that function called customScripts.updateNamedHook which supports both text inputs and text area elements, and I think it's name more clearly describes what the function does.
    If you are interested the code for the function can be found here, if you wish to use it all you need to do replace the existing Javascript code in the Story Javascript area with the code in that link and then replace each of the three occurrences of customScripts.submitName within your button element with customScripts.updateNamedHook.
  • Thanks I'll do that.
Sign In or Register to comment.