Harlowe 3.1.0 - user input leads to "I can't find a '3rd' data name in an array" message

I keep on getting an error message and am not sure how to fix it. I have pasted below my code as well as some background information.

Thank you in advance to all those that are willing to help :slight_smile:

Some Background

  • The code as it stands right now asks the user for their name.
  • I have already figured out how to make it so that the username cannot include spaces or special characters
  • I am using Harlowe 3.1.0 in Twine 2
  • Passage 2 below is giving me issues

What I Am Trying To Accomplish

  • I am having difficulty in making it so that the user cannot only put 2 letters --> I keep on getting the following error message: "I can’t find a ‘3rd’ data name in an array"
  • I haven’t delved into it yet, but I’d also like to make it so that you cannot have three of the same letters in a row. So if the user provides “nathanfff” it’ll make them retype the name just like if they write “hhh”

And now, onto the code:


1st passage in relevant sequence (titled: “User Name”)

Before we begin, what is your ''//first name//''?

<input type="text" data-varname="userName">

<span style="font-size: 4vw;">(link-goto: "Done", "Story Choice")</span><span style="color:#838383;font-size: 1.75vw;"> <-- Click this when you finished writing your name!</span>
<script>processInputElements();</script>

2nd passage in relevant sequence (titled: “Story Choice”) --> this is the passage giving me issues

(if: (text:(a: 0,1,2,3,4,5,6,7,8,9, "!","@","#","$","%","^","&","*","(",")","-","_","+","=","{","[","}","]",":",";",'"',"'","<",".",">",",","?","/", "~", "`")) contains any of (text: $userName))[(align: "=><=")[Click "OK" to return to the previous page(alert:"Please provide a valid name")(undo:)]](else-if: (" ") contains any of (text: $userName))[(align: "=><=")[Click "OK" to return to the previous page(alert: "Please provide your first name only")(undo:)]](else-if:  $userName's 3rd is " ")[(align: "=><=")[Click "OK" to return to the previous page(alert: "Please provide a name with at least 3 letters")(undo:)]](else:)[(align: "<==")[It's nice to meet you (upperfirst: $userName)!]

(set: $a to (shuffled: "story", "story2", "story3"))
Select question:
(set: $story to "this does not matter")
(dropdown: bind $story, $a's 1st, $a's 2nd, $a's 3rd)

{
(link: "Go!")[
	(if: $story is "story")[(go-to: "story1")]
	(else-if: $story is "story2")[(go-to: "story2")]
	(else-if: $story is "story3")[(go-to: "story3")]
]
}
]

Note to reader: you’re an expect Harlowe Twine programmer so you probably already know this :wink: --> the second passage in this sequence opens up three new passages (“story1”, “story2”, and “story3”). I did not pase those passages here as they weren’t giving me any sort of issues!

EDIT: Replaced the GitHub based link with the TwineLab based one.

If you plan to hack the story format to add extra functionality (like a text-field input) then I strongly suggest to consider using Chapel’s Unofficial Custom Macro Framework for Harlowe add-on, as it includes with a (textbox:) macro example.

A Text Input field doesn’t pad the end of an entered value with space characters, so your (else-if: $userName's 3rd is " ") condition is going to cause an error is the end-used entered less than 3 characters. You may want to check the length of the String value instead…

(else-if:  $userName's length < 3)

I’m unsure why you are creating an Array of numbers & symbols only then to convert it into a coma separated String value using the (text:) macro. I wonder if it would be more efficient to just use a String literal like…

(if: "0123456789!@#$%^$*()-_+={[}]:;\"'<,>.?/-`" contains any of $userName)

I’m unsure why you are wrapping the single space character String literal in the following code within parenthesis…

(else-if: (" ") contains any of (text: $userName))

…the following should produce a similar result…

(else-if: $userName contains " ")

Thank you so much @Greyelf! I was unable to utilize the textbox feature (after downloading and uploading the javascript source code, I went into this website and the textbox documentation would not load: I got a “404” error message). That said, everything else you suggested worked really swimmingly well!!!

The compiled webpage is where you should be looking at this stuff. https://twinelab.net/harlowe-macro-api/

Here’s the textbox macro: https://twinelab.net/harlowe-macro-api/#/examples/textbox

Thank you @Chapel !! I’ll have to check it out later.

@Greyelf I’m sorry to bother about the same issue again. I just noticed that if the user presses done without writing any text, they receive this error message: you can’t get data values from the number 0.

How do I go by fixing that issues?

Additionally, I wanted to make it so that the user cannot write the same three letters in a row. I created some additional code lines (please see below) which works except that if a name contains 3 letters anywhere throughout the name, then it still gives an error message and I wanted to make it so that the user only receives an error message if they utilize those three letters in a row.

Passage 1 - unchanged from above

Before we begin, what is your ''//first name//''?
<input type="text" data-varname="userName">

<span style="font-size: 4vw;">(link-goto: "Done", "Story Choice")</span><span style="color:#838383;font-size: 1.75vw;"> <-- Click this when you finished writing your name!</span>
<script>processInputElements();</script>

Passage 2

(if: "0123456789!@#$%^&*()-_+={[}]:;\"|'<,>.?/`~" contains any of $userName)[(align: "=><=")[Click "OK" to return to the previous page(alert:"Please provide a valid name")(undo:)]](else-if:  $userName's length is < 3)[(align: "=><=")[Click "OK" to return to the previous page(alert: "Please provide a name with at least 3 letters")(undo:)]](else-if: "Aaa" or "Bbb" or "Ccc" or "Ddd" or "Eee" or "Fff" or "Ggg" or "Hhh" or "Iii" or "Jjj" or "Kkk" or "Lll" or "Mmm" or "Nnn" or "Ooo" or "Ppp" or "Qqq" or "Rrr" or "Sss" or "Ttt" or "Uuu" or "Vvv" or "Www" or "Xxx" or "Yyy" or "Zzz" contains any of $userName)[(align: "=><=")[Click "OK" to return to the previous page(alert:"Please provide a valid name")(undo:)]](else-if: $userName contains " ")[(align: "=><=")[Click "OK" to return to the previous page(alert: "Please provide your first name only")(undo:)]](else:)[(align: "<==")[It's nice to meet you (upperfirst: $userName)!]

(set: $a to (shuffled: "story", "story2", "story3"))
Select question:
(set: $story to "this does not matter")
(dropdown: bind $story, $a's 1st, $a's 2nd, $a's 3rd)

{
(link: "Go!")[
	(if: $story is "story")[(go-to: "story1")]
	(else-if: $story is "story2")[(go-to: "story2")]
	(else-if: $story is "story3")[(go-to: "story3")]
]
}
]

On a Different note - I was having some difficulty installing the textbox feature on my computer for some reason, so I tried playing around with the prompt functionality, but I didn’t know how to make the prompt information work with the code lines above to ultimately get at the same result. I’m sure you know how to do it so I figured I’d ask!

Thank you in advance for all of your help @Greyelf!!!

Looking for a sequence of three consecutive instances of the same character isn’t easy to do using just Harlowe built-in features. The following is one possible method you can use…

(note: For this example I am skipping the end-user input step and using a hard-wired value for the $userName variable instead.)

(set: $userName to "abcDddefg")

{
	(set: $found to false)
	(if: $userName's length > 0)[
		(set: _chars to (lowercase: $userName))
		(set: $last to "")
		(set: $count to 1)
		(for: each _chr, ..._chars)[
			(unless: $found)[
				(if: _chr is $last)[
					(set: $count to it + 1)
				]
				(else:)[
					(set: $count to 1)
				]
				(set: $last to _chr)
				(set: $found to ($count is 3))
			]
		]
	]

	(if: $found)[
		Found a sequence of three $last characters.
	]
}

The above uses the (lowercase:) macro to make it easier to compare one character within another, because String comparison is case sensitive.
eg. “D” and “d” are not equal, as the following demonstrates.

(if: "D" is "d")[Case insensitive]

I used the (for:) macro to examine each character (_chr) within the lowercase String (_chars) and incremented a counter if the ‘current’ character was same as the ‘last’ one, I reset the counter if they weren’t. Once the counter reached 3 I set the Boolean $found variable to true, this same variable was used with a (unless:) macro to halt the comparison of chars once a sequence had been found.

Thank you for all of this information @Greyelf!! It worked for the most part, but now I am running into some other issues:

  1. If there are multiple instances of triple letters being the same, then it is coming up as valid (i.e. if a user writes “nnNsSsffF”).
  2. The dialogue box behind the warning box is large - is there a way to lessen the spacing so that the warning behind the black box appears in a smaller dialogue box? I tried deleting some of the spacing, but then the code that you created stopped working.
  3. Whenever a user clicks on the “done” button without writing any text, it is giving the error of: “you can’t get data values from the number 0.”

I have pasted my code below in case it is relevant for a frame of reference. Thank you so much @Greyelf for helping me so much with all of this!!

{

	(set: $found to false)

	(if: $userName's length > 0)[

		(set: _chars to (lowercase: $userName))

		(set: $last to "")

		(set: $count to 1)

		(for: each _chr, ..._chars)[

			(unless: $found)[

				(if: _chr is $last)[

					(set: $count to it + 1)

				]

				(else:)[

					(set: $count to 1)

				]

				(set: $last to _chr)

				(set: $found to ($count is 3))

			]

		]

	]

	(if: $found)[

		[(align: "=><=")[Click "OK" to return to the previous page(alert:"Please provide a valid name")(undo:)]]

	]

}
(if: "0123456789!@#$%^&*()-_+={[}]:;\"|'<,>.?/`~" contains any of $userName)[(align: "=><=")[Click "OK" to return to the previous page(alert:"Please provide a valid name")(undo:)]](else-if: $userName contains " ")[(align: "=><=")[Click "OK" to return to the previous page(alert: "Please provide your first name only")(undo:)]](else-if:  $userName's length is < 3)[(align: "=><=")[Click "OK" to return to the previous page(alert: "Please provide a name with at least 3 letters")(undo:)]](else:)[(align: "<==")[It's nice to meet you (upperfirst: $userName)!]

(set: $a to (shuffled: "story", "story2", "story3"))
Select question:
(set: $story to "this does not matter")
(dropdown: bind $story, $a's 1st, $a's 2nd, $a's 3rd)

{
(link: "Go!")[
	(if: $story is "story")[(go-to: "Story1 loading screen")]
	(else-if: $story is "story2")[(go-to: "Story2 loading screen")]
	(else-if: $story is "story3")[(go-to: "Story3 loading screen")]
]
}
]

Ideally your (if: $found) macro call should be part of your other (if:) and (else-if:) chain. So something like the following…

(if: $found)[
	[(align: "=><=")[Click "OK" to return to the previous page(alert:"Please provide a valid name")(undo:)]]
]
(else-if: "0123456789!@#$%^&*()-_+={[}]:;\"|'<,>.?/`~" contains any of $userName)[ ... ] ....
...

“you can’t get data values from the number 0.”

If you don’t pre-initialise a story variable before you use it then Harlowe will assign it a default value of zero, and you can’t access properties / elements (data values) from a number.

Without seeing the code of your processInputElements() function I have to assume that works similarly to the one found in this posting on a Twine related forums, in that it attaches an on “change” event handler that updates an associated Story variable whenever the a input field is changed. And if the end-user doesn’t enter a value within the field then the associated variable is not assigned a value by that event handler.

You likely need to add code like the following some time before asking for the end-user’s input.

(set: $userName to "")

You may notice that I wrapped my example for looking for a sequence of characters within Collapsing White-space markup, this was to reduce the number of line-breaks that example produced. You could try wrapping the whole of your code example (relating to this topic) within such markup.

I must be driving you crazy @Greyelf :sweat_smile: First off - your suggestions with setting the userName to “” and putting all of the if else-if in one segment worked!! - Thank you :slight_smile: .

Secondly, I tried implementing different variations of the curly brackets as you had suggested to get rid of whitespace. None worked, but this one was the best that I could get - the dialogue boxes were no longer huge, but the the message “click OK to return to the previous page” is not appearing. I have pasted below the harlowe code as well as the js code for the processInputElements()

Harlowe Code:

{	(set: $found to false)

	(if: $userName's length > 0)[

		(set: _chars to (lowercase: $userName))

		(set: $last to "")

		(set: $count to 1)

		(for: each _chr, ..._chars)[

			(unless: $found)[

				(if: _chr is $last)[

					(set: $count to it + 1)

				]

				(else:)[

					(set: $count to 1)

				]

				(set: $last to _chr)

				(set: $found to ($count is 3))

			]

		]

	]
	}
{(if: $found)[(align: "=><=")[Click "OK" to return to the previous page(alert:"Please provide a valid name")(undo:)]](else-if: "0123456789!@#$%^&*()-_+={[}]:;\"|'<,>.?/`~" contains any of $userName)[(align: "=><=")[Click "OK" to return to the previous page(alert:"Please provide a valid name")(undo:)]](else-if: $userName contains " ")[(align: "=><=")[Click "OK" to return to the previous page(alert: "Please provide your first name only")(undo:)]](else-if:  $userName's length is < 3)[(align: "=><=")[Click "OK" to return to the previous page(alert: "Please provide a name with at least 3 letters")(undo:)]](else:)[(align: "<==")[It's nice to meet you (upperfirst: $userName)!]]
(link: "Go!")[
	(if: $story is "story")[(go-to: "Story1 loading screen")]
	(else-if: $story is "story2")[(go-to: "Story2 loading screen")]
	(else-if: $story is "story3")[(go-to: "Story3 loading screen")]
]}

Js Code

window.Harlowe = { 'State' : State };
window.processInputElements = function () {
    $('input[data-varname]').on('change', function () {
        var varName = $(this).attr('data-varname');
        Harlowe.State.variables[varName] = this.value;
    });
		$('select[data-varname]').on('change', function () {
        var varName = $(this).attr('data-varname');
        Harlowe.State.variables[varName] = this.value;
    });
};

I really appreciate all of your help in all of this! I’m doing this for a voter engagement project and the harlowe game is coming along! Honestly, between these posts and other posts that you’ve replied to other people previously - you’ve really helped me enhance the functionality of the project!

Oh and I appreciate the explanations you provide when answering people - it helps me better understand how the harlowe developer thought when they created this and so that’s helped me ultimately in having to look up less online.

I re-formatted you example as follows, so it was a little more readable. you will note I wrap the whole of the code within a single set of curly braces.

{
	(set: $found to false)
	(if: $userName's length > 0)[
		(set: _chars to (lowercase: $userName))
		(set: $last to "")
		(set: $count to 1)
		(for: each _chr, ..._chars)[
			(unless: $found)[
				(if: _chr is $last)[
					(set: $count to it + 1)
				]
				(else:)[
					(set: $count to 1)
				]
				(set: $last to _chr)
				(set: $found to ($count is 3))
			]
		]
	]
	(if: $found)[
		(align: "=><=")[
			Click "OK" to return to the previous page
			(alert: "Please provide a valid name")
			(undo:)
		]
	]
	(else-if: "0123456789!@#$%^&*()-_+={[}]:;\"|'<,>.?/`~" contains any of $userName)[
		(align: "=><=")[
			Click "OK" to return to the previous page
			(alert: "Please provide a valid name")
			(undo:)
		]
	]
	(else-if: $userName contains " ")[
		(align: "=><=")[
			Click "OK" to return to the previous page
			(alert: "Please provide your first name only")
			(undo:)
		]
	]
	(else-if: $userName's length is < 3)[
		(align: "=><=")[
			Click "OK" to return to the previous page
			(alert: "Please provide a name with at least 3 letters")
			(undo:)
		]
	]
	(else:)[
		(align: "<==")[It's nice to meet you (upperfirst: $userName)!]
	]
	(link: "Go!")[
		(if: $story is "story")[(go-to: "Story1 loading screen")]
		(else-if: $story is "story2")[(go-to: "Story2 loading screen")]
		(else-if: $story is "story3")[(go-to: "Story3 loading screen")]
	]
}

I believe there is a mis-understanding on how a (alert:) dialog works in relation to the ‘Click “OK” to return to the previous page’ message you are displaying.

Simply put the text message is added to the visual page and then the alert ‘dialog’ is shown above that page, so any page content that is located directly under the area the ‘dialog’ is being shown in will be hidden. The styling being used to display the dialog also alters the alpha value of the colour of the textual content of the page, so that text appears dark greyish. If you resize your web-browser frame you may be able to see the message on the page.

1 Like

Replying in the same feed as I found a solution in case it may help someone in the future. I used three passages: one that asks for the user name, one that determines whether the user name is valid or not (if it isn’t valid it determines what kind of invalid variable has arisen) and a third that accepts the type of invalidity of the username and either reverts to the next segment of the text or goes back to the passage requesting for the username. This enabled for more versatility in terms of editing the text behind the alert box…

Big shoutout to @Greyelf who really helped with the majority of this code!

Passage 1 - titled: User Name

{(set: $userName to "")
Before we begin, what is your ''//first name//''?}
<input type="text" data-varname="userName">

<span style="font-size: 4vw;">(link-goto: "Done", "Story Choice")</span><span style="color:#838383;font-size: 1.75vw;"> <-- Click this when you finished writing your name!</span>
<script>processInputElements();</script>

Passage 2 - titled: Story Choice

{

	(set: $found to false)

	(if: $userName's length > 0)[

		(set: _chars to (lowercase: $userName))

		(set: $last to "")

		(set: $count to 1)

		(for: each _chr, ..._chars)[

			(unless: $found)[

				(if: _chr is $last)[

					(set: $count to it + 1)

				]

				(else:)[

					(set: $count to 1)

				]

				(set: $last to _chr)

				(set: $found to ($count is 3))

			]

		]

	]

(if: $found)[(set: $userNameError to "Invalid")(go-to: "Invalid Name Check")](else-if: $userName is "")[(set: $userNameError to "no text")(go-to: "Invalid Name Check")](else-if: "0123456789!@#$%^&*()-_+={[}]:;\"|'<,>.?/`~" contains any of $userName)[(set: $userNameError to "Invalid")(go-to: "Invalid Name Check")](else-if: $userName contains " ")[(set: $userNameError to "not first name")(go-to: "Invalid Name Check")](else-if:  $userName's length is < 3)[(set: $userNameError to "not three letters")(go-to: "Invalid Name Check")](else:)[(align: "<==")[It's nice to meet you (upperfirst: $userName)!]]}
(set: $a to (shuffled: "story", "story2", "story3"))
Please select a story and press play when you're ready:
(set: $story to "this does not matter")
(dropdown: bind $story, $a's 1st, $a's 2nd, $a's 3rd)[[<p>Play</p>->loading screen]]

Passage 3 - titled: Invalid Name Check

{(if: $userNameError is "no text")[Click "OK" to return to the previous page(alert:'Please write your name in the text box and press <u>Done</u>')(go-to: "User Name")](else-if: $userNameError is "Invalid")[Click "OK" to return to the previous page(alert:"Please provide a valid name")(go-to: "User Name")](else-if: $userNameError is "not first name")[Click "OK" to return to the previous page(alert:"Please provide your first name only")(go-to: "User Name")](else-if: $userNameError is "not three letters")[Click "OK" to return to the previous page(alert:"Please provide a name with at least 3 letters")(go-to: "User Name")]}

Of course you’ll also need to use the following CSS code

window.Harlowe = { 'State' : State };
window.processInputElements = function () {
    $('input[data-varname]').on('change', function () {
        var varName = $(this).attr('data-varname');
        Harlowe.State.variables[varName] = this.value;
    });
		$('select[data-varname]').on('change', function () {
        var varName = $(this).attr('data-varname');
        Harlowe.State.variables[varName] = this.value;
    });
};