How to get a set outcome(s) from an input box using keywords (arrays inside a datamap)

Twine 2.5.1
Harlowe 3.3.3

Hello, first of all I’m new to all of this, but trying my best, forgive me if my code is ugly.
I’m trying to get a set outcome (or outcomes) from an input box. Once the box is filled, the outcome will print below the input box. At first I was using else-if statements, but it was getting very long, and some keywords (labeled as items in my example code) are present for multiple Xs, and the else-ifs would stop at the first met condition, when I want all outcomes that meet the conditions to show. So I attempted using a datamap, trying to tie all the Xs and their outcomes into one variable, and having it all checked at once. It does not work.

The error I get says

Only strings and numbers can be used as data names for an empty datamap, not an array (with the string “item1”, the string “item2”, ).

This is on my startup passage

(set: $X1 to (a: "item1", "item2", "item3")
(set: $X2 to (a: "item3", "item4", "item5")
(set: $checkX to (dm: $X1, "outcome 1", $X2, "outcome 2"))

and then on the actual input screen is

(input-box: bind $userfill)
(live: 1s)[
(if: $userfill is "")[]
(else-if: $userfill contains some of $checkX)[(print: $checkX's 2nd)]
(else:)[only shows when not blank and no items]

here I’m trying to get the listed outcome from whatever X the conditions met.
So, if the input box has item1 or item2, the outcome will be “outcome 1”. If it has item4 or item5, it’ll be “outcome 2”. If it’s item3, the outcome would be both “outcome 1” and “outcome 2”

it did work initially when I had

(else-if: $userfill contains some of $X1)[outcome 1]
(else-if: $userfill contains some of $X2)[outcome 2]

but it was getting very very long (there’s 28 outcomes, and I can’t lessen the amount), and like I said previously, it would stop at the first met condition when I wanted it to show every outcome the conditions matched. Would I just need to use if statements rather than else-ifs to get that to work? I’d prefer a neater solution, but anything that works is better than what doesn’t. Is there a better way to go about what I’m doing?

If I understand the issue correctly then the following is one possible solution to achieve the result you want. I strongly suggest you lookup the documentation of any macro / feature used that you don’t currently know.

1: Change the data-map within your startup tagged Passage to something like the following, which associates each known “outcome” with its items.

(set: $possible to (dm:
	"outcome 1", (a: "item1", "item2", "item3"),
	"outcome 2", (a: "item3", "item4", "item5")

note: My data-map is basically the inverse of your own, which I did because the “name” part of a name/value pair should be a String value. I also got rid of the interim $X1 and $X2 variables because the served no purpose in my solution.

2: Place the following within the Passage you want the data entry and value processing to be done in.

(set: $input to "")

(input-box: bind $input)


(link-repeat: "Check")[{
	<!-- Clean the String value supplied by the end-user -->
	(replace: ?message)[]
	(set: $outcomes to (a:))

	<!-- 1. Remove any leading or trailing whitespace characters -->
	(set: _value to (trimmed: $input))

	<!-- 2. Display a warning if no value was entered. -->
	(if: _value is "")[
		(set: $input to "")
		(replace: ?message)[Please enter one or more items.]
		<!-- 3. Convert value to a single letter casing, so it can be compared to known items later. -->
		(set: _value to (lowercase: _value))

		<!-- [optional] remove any invalid characters (like ,'".) from value -->

		<!-- 4. Split the value into an Array of items -->
		(set: _items to (words: _value))

		<!-- 5. Loop through the possible outcomes and entered items to find any matches -->
		(for: each _outcome, ...(dm-names: $possible))[
			(for: each _item, ..._items)[
				<!-- Does the list of possible items associated with this outcome contain this entered item? -->
				(if: $possible's (_outcome) contains _item)[
					<!-- Add this outcome to the list, unless it's already there. -->
					(unless: $outcomes contains _outcome)[
						(set: $outcomes to it + (a: _outcome))

		<!-- 6. Display any outcomes, if any -->
		(if: $outcomes's length > 0)[
			(replace: ?message)[Outcomes found: (print: $outcomes)]
			(replace: ?message)[No outcomes found!]


  • the HTML Comments I included in the above can be safely removed, there are only there to help make the purpose of each part of the code clear.
  • I replaced your usage of a (live:) macro with a link for two reasons: the timer can interfere with the end-user’s ability to interact with the page; the timer could fire while the end-user is still typing, which would lead to a false result being reported.

Thank you so much for your help!
I have the documentation open, I’ve got two windows so I can check between different macros and special words and whatnot, but that doesn’t always mean it computes haha.
I was able to take your solution and slightly modify it to suit my needs, and it works! Thank you so so much. I also learned what the for loop does, which while I had read the documentation, I didn’t understand it at all and thus discarded it. But now I’ve circled back and realized what it does and thank you so much for that. I’m entirely out of my depth with computer techy things, but I really wanted to see my vision come to fruition, and you’ve helped a great deal towards that goal.