RegEx help - ask a question that requires two specific words in the answer
Jamie Furlong
24 Aug 2018, 05:27I've checked the documentation on asking a question but I have a slightly more complicated requirement. I want to ask the question "which two colours make up...?", where the answer must include both 'red' and 'black'. I need to make sure that their answer can handle upper or lower case, the word 'and' or '&' or hyphen or '/' or any other permutation, but be incorrect if they say 'red and green' or 'black and green' (so both colours must be correct).
I'm guessing it will be along these lines:
msg ("What color is the marker?")
JS.eval ("$('#txtCommand').attr('placeholder', 'Your answer');")
JS.panesVisible (false)
get input {
if (IsRegexMatch ("^(a )?(black + red)$", LCase (result))) {
msg ("Correct")
}
else {
msg ("'Is it \"" + result + "\"?' you ask.")
msg ("'No!'")
}
JS.eval ("$('#txtCommand').attr('placeholder', 'Type here...');")
JS.panesVisible (true)
}
... though this doesn't work. I've tried
if (IsRegexMatch ("^(a )?(=.*black)(=.*red)$", LCase (result))) {
and similar.
Can anyone assist? Thanks.
The Pixie
24 Aug 2018, 06:39There are all sorts of tricks you can do with a regex, so this may not be the most elegant solution, but what I would do is two tests, one for each colour. In fact, regex may be overkill here; you can use Instr
to determine if one string is in another (it gives the position, so if it returns a number greater than zero then it is).
get input {
result = LCase(result)
if (Instr(result, "red") > 0 and Instr(result, "black") > 0) {
mrangel
24 Aug 2018, 09:31The Pixie's solution is good, but beware that if the player realises you're using that method they could just answer "red orange green blue black yellow purple white grey" or something.
A slightly more complex method (that would probably be better in a function, so you can use the same code with different answer lists):
<function name="CheckAnswer" type="boolean" parameters="result, required_words, optional_words">
remaining_words = ListExclude (required_words, "")
result = LCase(result) + " "
word = ""
for (i, 1, LengthOf(result)) {
letter = Mid (result, i, 1)
if (IsRegexMatch ("\\w", letter) {
word = word + letter
}
else if (not word = "") {
if (ListContains (remaining_words, word)) {
list remove (remaining_words, word)
list add (optional_words, word)
}
else if (not ListContains (optional_words, word)) {
return (false)
}
word = ""
}
}
return (ListCount (remaining_words) = 0)
</function>
You'd call that like:
if (CheckAnswer (result, Split("red;black"), Split("and"))) {
Or, if you really want a regex, ^\W*(red|black)(?:\W|\band\b)*(?!\1)(red|black)\W*$
should do the job (remembering that if you're entering it between quotes in code view you'll have to double up the backslashes to stop the quotes trying to interpret them).
In this case, using \W
to match non-word characters. I could have used [^a-z]
or similar; but \w
(word characters) and \W
(non-word characters) will usually behave sanely if the player inputs accented letters.
Jamie Furlong
24 Aug 2018, 09:32@The Pixie
Wow, that's surprisingly simple. I like that it allows for additional characters between the two strings. Very useful, thank you.
Jamie Furlong
24 Aug 2018, 09:35@Mrangel
Ha, just checked it and you're right. I'll have a go at your solution.
Jamie Furlong
24 Aug 2018, 10:05OK, I think there was a missing closing bracket so in my room I now have
msg ("What color is the marker?")
JS.eval ("$('#txtCommand').attr('placeholder', 'Your answer');")
JS.panesVisible (false)
get input {
if (CheckAnswer (result, Split("red;black"), Split("and"))) {
msg ("Correct")
}
else {
msg ("'Is it \"" + result + "\"?' you ask.")
msg ("'No!'")
}
JS.eval ("$('#txtCommand').attr('placeholder', 'Type here...');")
JS.panesVisible (true)
}
I have created a function called CheckAnswer and set the return type to Boolean and added the three parameters you suggested. However I'm slipping up on what I include in the Script section. I have pasted
remaining_words = ListExclude (required_words, "")
result = LCase(result) + " "
word = ""
for (i, 1, LengthOf(result) {
letter = Mid (result, i, 1)
if (IsRegexMatch ("\\w", letter) {
word = word + letter
}
else if (not word = "") {
if (ListContains (remaining_words, word)) {
list remove (remaining_words, word)
list add (optional_words, word)
}
else if (not ListContains (optional_words, word)) {
return (false)
}
word = ""
}
}
return (ListCount (remaining_words) = 0)
But it cannot run the script. Rather frustratingly I can't do a screen print to show you what's happening but within the function it is putting in red everything between and including the lines
for (i, 1, LengthOf(result) {'
and
word = ""
}
}
The error message I get when I run the game is
Error: Error adding script attribute 'enter' to element 'Q018': Invalid variable name ''
mrangel
24 Aug 2018, 10:23Sorry, missing )
.
That's the downside of typing code on the forum, I don't see the errors right away.
Should be: for (i, 1, LengthOf(result)) {
Jamie Furlong
24 Aug 2018, 10:37OK, I found another missing ). It's accepted all the scripts now but I'm still getting that error message
Error adding script attribute 'enter' to element 'Q018': Invalid variable name ''
mrangel
24 Aug 2018, 10:56I can't work out what I'm missing here.
I assume this room is Q018, and this is the 'enter' script. So the error must be in that piece of code somewhere, but I just can't see it. The only time I've seen Invalid variable name ''
before is from a =
on its own on a line; but I have to assume there's something that it's failing to parse somehow.
Selsynn
24 Aug 2018, 11:01Have you seen this error
if (IsRegexMatch ("\\w", letter) {
seems to miss a )
(I remember a interview for a programming job where I had just an hour to detect most errors in two pages of code in different languages)
Jamie Furlong
24 Aug 2018, 11:04That's the one I found earlier, Selsynn, but well spotted.
Jamie Furlong
24 Aug 2018, 11:14mrangel - yes, Q018 is the room. In theory the entire code should be able to run on one page (I deleted the function I created and just put it all on one page).
msg ("What color is the marker?")
request (Show, "Command")
<function___SPACE___name = "CheckAnswer" type="boolean" parameters="result, required_words, optional_words">
remaining_words = ListExclude (required_words, "")
result = LCase(result) + " "
word = ""
for (i, 1, LengthOf(result)) {
letter = Mid (result, i, 1)
if (IsRegexMatch ("\\w", letter)) {
word = word + letter
}
else if (not word = "") {
if (ListContains (remaining_words, word)) {
list remove (remaining_words, word)
list add (optional_words, word)
}
else if (not ListContains (optional_words, word)) {
return (false)
}
word = ""
}
}
return (ListCount (remaining_words) = 0)
</function>
JS.eval ("$('#txtCommand').attr('placeholder', 'Your answer');")
JS.panesVisible (false)
get input {
if (CheckAnswer (result, Split("red;black"), Split("and"))) {
msg ("Correct")
}
else {
msg ("'Is it \"" + result + "\"?' you ask.")
msg ("'No!'")
}
JS.eval ("$('#txtCommand').attr('placeholder', 'Type here...');")
JS.panesVisible (true)
}
mrangel
24 Aug 2018, 15:57Does that work? I didn't think you could do local functions like that.
I figured it was easier to make a function because then you can just return (false)
to exit the loop early (as Quest doesn't seem to have a break statement).
Without using functions, it could be:
msg ("What color is the marker?")
request (Show, "Command")
JS.eval ("$('#txtCommand').attr('placeholder', 'Your answer');")
JS.panesVisible (false)
get input {
remaining_words = Split("red;black")
optional_words = Split("and")
wrong = false
result = LCase(result) + " "
word = ""
for (i, 1, LengthOf(result)) {
letter = Mid (result, i, 1)
if (IsRegexMatch ("\\w", letter)) {
word = word + letter
}
else if (not word = "") {
if (ListContains (remaining_words, word)) {
list remove (remaining_words, word)
list add (optional_words, word)
}
else if (not ListContains (optional_words, word)) {
wrong = true
}
word = ""
}
}
if (ListCount (remaining_words) = 0 and not wrong) {
msg ("Correct")
}
else {
msg ("'Is it \"" + result + "\"?' you ask.")
msg ("'No!'")
}
JS.eval ("$('#txtCommand').attr('placeholder', 'Type here...');")
JS.panesVisible (true)
}
Jamie Furlong
25 Aug 2018, 01:25Morning mrangel,
Unfortunately that killed Quest altogether on the first run. I tried pasting the code into a new game and I'm getting that same error message:
Error: Error adding script attribute 'enter' to element 'room': Function not found: '}'
hegemonkhan
25 Aug 2018, 08:28(filler for getting my edited post, updated/posted)
(again, filler for getting my edited post, updated/posted)
@ Jamie Furlong:
there's just a missing beginning curly brace, '{', (the Error isn't that clear, as it shows the ending curly brace, '}', as it tried to parse the quest code but since it had a missing curly brace symbol, it causes the entire code to be shifted/changed when the parser tries to understand it, and thus the mis-leading error message with the wrong curly brace being said as missing) character/symbol in mrangel's code on accident (when typing/writing/posting code on the fly, it's easy to have typos/missing characters/symbols, and etc stupid typing/writing/etc mistakes --- HK makes them all of the time!)
to understand how a parser works:
// this is correct syntax, so it's easy to read:
hi, my name is hk, what is your name?
but when we got the wrong syntax (in coding/scripting/programming), it causes the entire thing to shift around and all over the place, like so:
ish kw hat? him yna, mei syo urna, me
now, if you didn't have the correct syntax above, you would have no idea what this means! This is the same nightmare for the poor parser trying to understand the meaning of it, jsut as you and I are (imagine not being given the correct statement above, and trying to decipher what the hell I was trying to write/mean, lol)
and here is the line with the coding ERROR (missing beginning curly brace):
if (ListCount (remaining_words) = 0 and not wrong)
msg ("Correct")
}
if (ListCount (remaining_words) = 0 and not wrong) __ <------ missing the beginning curly brace: '{'
msg ("Correct")
}
here's the fix:
if (ListCount (remaining_words) = 0 and not wrong) {
msg ("Correct")
}
if (ListCount (remaining_words) = 0 and not wrong) { <------ added in the missing curly brace
msg ("Correct")
}
here's the full code with fix:
msg ("What color is the marker?")
request (Show, "Command")
JS.eval ("$('#txtCommand').attr('placeholder', 'Your answer');")
JS.panesVisible (false)
get input {
remaining_words = Split("red;black")
optional_words = Split("and")
wrong = false
result = LCase(result) + " "
word = ""
for (i, 1, LengthOf(result)) {
letter = Mid (result, i, 1)
if (IsRegexMatch ("\\w", letter)) {
word = word + letter
}
else if (not word = "") {
if (ListContains (remaining_words, word)) {
list remove (remaining_words, word)
list add (optional_words, word)
}
else if (not ListContains (optional_words, word)) {
wrong = true
}
word = ""
}
}
if (ListCount (remaining_words) = 0 and not wrong) {
msg ("Correct")
}
else {
msg ("'Is it \"" + result + "\"?' you ask.")
msg ("'No!'")
}
JS.eval ("$('#txtCommand').attr('placeholder', 'Type here...');")
JS.panesVisible (true)
}
in some other languages (I'm not sure if it can be done with quest or not), it's possible to do the code syntax structure like this:
if (XXX)
{
// scripting
}
else if (XXX)
{
// scripting
}
else
{
// scripting
}
which, while it uses up a lot more lines (which generally for probably most people it's nice to have as compact code as possible)... it makes spotting missing curly braces, parenthesis, and etc very very very easy to spot/check-for !!!
but again... quest may not be able to handle this syntax structure... not sure
mrangel
25 Aug 2018, 09:26Thanks HK; didn't spot that one right away.
hegemonkhan
25 Aug 2018, 09:37I'm good at finding missing/mismatch-extra/wrong-direction braces/parenthesis/tags/etc (and that bloody forward slash in the ending tags, that I always forgot to type in too)... HK is reknowned for having missing/mismatch-extra/wrong-direction braces/parenthesis/tags/slashes/etc in his own code... so he's gotten good at tracking them down...
Jamie Furlong
26 Aug 2018, 01:41@HK
Well done and thank you!
In another post you asked if I was new to coding. I've been coding for a long while but only html, CSS and very basic JS, so although I couldn't write the above script from scratch myself I should have spotted this simple omission. You know how it is, you can look at something 20 times and miss the obvious. I'm grateful to you, mrangel The Pixie and others for the support and suggestions you've offered.
Once again, thank you all.
Jamie Furlong
26 Aug 2018, 09:24One further question on this code:
Is it possible to use this again the game with a different question? I've tried copying the code and changing the
remaining_words = Split("red;black")
... but it returns an error. Is there anything else I need to change?
mrangel
26 Aug 2018, 14:14I think it should work fine. What kind of error does it give?
[Note that as written, that script will only work if the required words consist of letters, numbers, and underscores. So it won't work if one of the answer words is hyphenated. I could come up with a version to do that, but I think it would be a little more complicated]
Just because I can't get it out of my head
(This one is recursive to allow backtracking, so it has to be a function)<function name="CheckAnswer" type="boolean" parameters="answer, required, optional">
if (optional = null) optional = "\\W+;and;or"
if (TypeOf (required) = "string") required = Split (required)
if (TypeOf (optional) = "string") optional = Split (optional)
if (answer = "") {
return (ListCount (required) = 0)
}
list add (optional, "\\s+")
patterns = ListCompact (ListCombine (required, optional))
foreach (pattern, patterns) {
previous = ""
while (IsRegexMatch ("^"+pattern+previous, answer)) {
parts = Populate ("^(?<matched>"+pattern+")"+previous+"(?<remainder>.*)$", answer)
matched = StringDictionaryItem(parts, "matched")
remains = StringDictionaryItem(parts, "remainder")
newrequired = ListExclude (required, pattern)
if (CheckAnswer (remains, newrequired, patterns)) {
return (true)
}
else {
previous = previous + "(?<!^"+EscapeRegex(matched)+")"
}
}
}
return (false)
</function>
<function name="EscapeRegex" type="string" parameters="input">
characters = Split("\\;*;+;.;(;);[;];^;$;?;.;{;}")
result = ""
for (i, 1, LengthOf(input)) {
letter = Mid (input, i, 1)
if (ListContains (characters, letter)) result = result + "\\"
result = result + letter
}
return (result)
</function>
This one could be used as something like:
CheckAnswer (result, "red;black", "and;\\W+")
The required and optional parameters are lists of regular expressions. It will return true if the answer contains all the patterns in the "required" list, and doesn't contain anything that isn't in either list. As a shorthand, if you hand it a string like "red;black" it will split it first.
(EDIT: made sure it doesn't break when the player answers a valid regex as their answer. Just in case)
hegemonkhan
27 Aug 2018, 00:49@ Jamie Furlong
ah, web languages (html, css, js, xml, etc) programmer, I only learned the basics of it (simple web page creation)... it's another thing I need to learn more about using/doing... HK is lazy...
it's a bit difficult to switch (or learn amongst) between the different languages (web/scripting vs main/big programming languages: C++, Java, and Python, and all of the other ones, like MathLab, Ruby, Ruby on rails, and etc whatever and/or math-physics-robotics programming languages and/or the mobile device languages), but in knowing one or one type of them, you got some understanding at least, compared to someone totally new to coding/programming, as there's some similarities with all of the programming languages (needing to learn their unique syntaxes and etc features/concepts/etc that they all share)
Jamie Furlong
30 Aug 2018, 06:42Apologies, I've been traveling. Will pick this up again when I get back.
Jamie Furlong
30 Aug 2018, 08:13Hi again, just looking to implement a similar question. This time, instead of two words, I'd like either a word or a numeric, in this instance 'ten' or '10. I'm assuming the only line I need to change is
remaining_words = Split("red;black")
Do I still use 'split'? I'm assuming not, so what do I use instead? And do I change the ';' to '|'? I've tried:
remaining_words = Split("ten|10")
... but I'm assuming 'split' is for receiving two words. I'm looking for the code equivalent of 'either' and unfortunately I don't know enough to know what to search for!
mrangel
30 Aug 2018, 09:37The first example I posted doesn't have an option for "or".
The function I posted in my last post (unless I made any dumb typos in it) should be quite happy to handle "ten|10(?!\\d)"
, because each of the required words is a regex. (I this case, I put (?!\\d)
meaning "not followed by another digit" after the numbers, so that entering 1010
isn't accepted as being the right answer twice).