Inline status bar of links

As mentioned elsewhere, I’m currently toying with something akin to The Impossible Bottle’s hyperlink interface. It seems like such a great way to play IF on a touchscreen, and I want to experiment with it and see what directions it can be taken in.

Here’s the current plan:

Draw an inline status area every time the status bar is updated.

(redraw status bar) %% Overridden to also handle the inline bar
	(status bar @status) {
		(exhaust) *(status element)
	}
	(if) (link bar enabled) ~(opening cutscene) (then) %% For this game, disable it during the opening cutscene too
		(par)
		(inline status bar @inlinebar) {
			(exhaust) *(inline status element)
		}
	(endif)

(link bar enabled) lets players turn this off if it gets annoying.

Then, we can write whatever rules we want for (inline status element) to put them in this bar. For example, we always show LOOK, so that we’re not leaving an empty bar.

(inline status element) %% First, the obvious exits
	(link) {look}
	(current room $Room)
	(exhaust) {
		*(from $Room go $Dir to $Target)
		(direction $Dir)
		(if) (door $Target) (or) (room $Target) (then)
			(bullet) (link) (name $Dir)
		(endif)
	}

The way the Impossible Bottle one works, clicking on a noun in the text examines it, via the library’s built-in default actions system. But once you’ve examined it, you can see other verbs to try on it.

(global variable (last named object $)) %% Need to modify (notice player's $) and (forget pronouns out of scope) to update this

(inline status element) %% Then, the most recently named object
	(last named object $Obj)
	\| (link) (the $Obj)
	(exhaust) *(link viable actions for $Obj)

And in this game, you can only hold one thing at a time, so we can put the held object in the bar too.

(inline status element) %% Finally, anything we're currently holding
	(held $Obj)
	~(last named object $Obj)
	\| (link) (the $Obj)
	(exhaust) *(link viable actions for $Obj)

(held $Obj)
	(current player $Player)
	($Obj is #heldby $Player)

Now, Impossible Bottle I believe used the pronoun values instead of tracking a single last named object, which means TAKE IT and such are viable ways to phrase the command. But I don’t want to risk bugs when the last named object and the pronouns get out of sync, especially since I don’t fully understand the difference between “player’s it” and “narrator’s it”.

So, a convenience predicate to phrase our links nicely:

(link action $Action with $Obj) %% [take $] #apple -> take apple, take it
	(collect words)
		(the $Obj)
	(into $Noun)
	(collect words)
		(them $Obj)
	(into $Pronoun)
	(splice list $Noun into $Action to make $Link)
	(splice list $Pronoun into $Action to make $Text)
	(fully bound $Text) %% Avoid compiler warnings
	(bullet) (link $Link) (print words $Text)

And the helper to make it work:

%% splice list [b c] into [a $ d] to make [a b c d]
(splice list $Inner into [(bound $Head) | $Tail] to make [$Head | $NewTail])
	(splice list $Inner into $Tail to make $NewTail)

(splice list $Inner into [$ | $Tail] to make $Out)
	(append $Inner $Tail $Out)

Now we can have a whole bunch of rules like this, to present the different actions:

(link viable actions for $Obj)
	(item $Obj)
	~(held $Obj)
	(link action [take $] with $Obj)

So far, the actions I’ve included are:

  • If item and not held, TAKE $
  • If held, DROP $
  • If held and edible, EAT $
  • If actor container or actor supporter, ENTER $ or CLIMB $
  • If openable, OPEN $ or CLOSE $
  • If switchable, SWITCH $ ON or SWITCH $ OFF
  • If container or supporter, SEARCH $
  • If accepting objects behind or under, LOOK BEHIND/UNDER $
  • If pushable, PUSH $
  • If held and another person is visible, GIVE $

And the especially complicated one:

(link viable actions for $Obj) %% This is the complicated one: PUT
	(held $Obj)
	%% We need to duplicate some work from (link action $ with $) here
	(collect words)
		(the $Obj)
	(into $Noun)
	(collect words)
		(them $Obj)
	(into $Pronoun)
	(splice list $Noun into [put $ on] to make $OnLink)
	(splice list $Pronoun into [put $ on] to make $OnText)
	(fully bound $OnText)
	(bullet) (link $OnLink) (print words $OnText)
	\(
	(splice list $Noun into [put $ in] to make $InLink)
	(link $InLink) in
	, 
	(splice list $Noun into [put $ under] to make $UnderLink)
	(link $UnderLink) under
	, 
	(splice list $Noun into [put $ behind] to make $BehindLink)
	(link $BehindLink) behind
	\) something

Which makes a link like “put it in (on, under, behind) something” that links to PUT $ IN, PUT $ UNDER, etc.

How is this looking so far? Any horrible coding practices that’ll bite me in the back later, or design decisions that are a bad idea to start with? Or things that you think would improve the final product a lot?

This is nice! I would like to comment on some of it.

You are using an unbound parameter as template placeholder, I would have chosen an empty list (like in the standard library).

(splice list [b c] into [a [] d] to make [a b c d])

This will also enable to redefine (splice ...) rule:

(splice list $Inner into $List to make $Spliced)
	(split $List by [] into $Left and $Right)
	(append $Left $Inner $Appended)
	(append $Appended $Right $Spliced)

But that’s just a personal preference, your way works just as good.

I am fond of compartmentalizing specific logic into their own rules. So I am not a fan of (link action ...)'s processing spilling into the viable action for put. I would have added a closure parameter to it:

%% [take $] #apple [] -> take apple, take it
%% [put $ in] #apple {in} -> put apple in, in
(link action $Action with $Obj using $Template)
	(collect words)
		(the $Obj)
	(into $Noun)
	(collect words)
		(them $Obj)
	(into $Pronoun)
	(splice list $Noun into $Action to make $Link)
	(if) (empty $Template) (then)
		(splice list $Pronoun into $Action to make $Text)
		(fully bound $Text) %% Avoid compiler warnings
		(button) (link $Link) (print words $Text)
	(else)
		(link $Link) (query $Template)
	(endif)

Which allows for:

%% TAKE
(link viable actions for $Obj)
	(item $Obj)
	~(held $Obj)
	(link action [take $] with $Obj using [])

%% PUT
(link viable actions for $Obj) %% This is the complicated one: PUT
	(held $Obj)
	(link action [put $ on] with $Obj using [])
	\(
	(link action [put $ in] with $Obj using {in})
	,
	(link action [put $ under] with $Obj using {under})
	,
	(link action [put $ behind] with $Obj using {behind})
	\) something

Edit: I believe I have forgotten the query (button) from your original code. Now added.

1 Like

Makes sense! I was using $ because that’s how action patterns are written in grammar lines (before the grammar transformers get to them), but being able to split by [] does make that a lot simpler.

And it’s certainly easy enough to have another version of the predicate that doesn’t take a closure, and just calls the standard one with [] in that positoin.

1 Like

Do you mean like this?

@(link action $Action with $Obj)
	(link action $Action with $Obj using [])

Yes, that’s makes code more readable and elegant than querying all simple ones with empty lists.
Btw, if it’s only lowercase letters that can happen in those texts, one can certainly use lists instead of closures. Closures power lies in that they can do all sorts of computations in addition to text output. They can also output text with arbitrarily uppercase/lowercase combinations, which lists can’t do because every text character word in a list is converted to all lowercase dictionary words by the compiler.

One other advantage of using [] instead of $, is that you can use the query

(recover implicit action $Template $Obj into $Action)

to generate the action directly and bypass the parser if you want to.

I’m curious, how would that work with links?

Oh wait, those are links right? Yeah that would require modifying/rewriting library’s link mechanism rules; but bypassing all the scope/likelihood/disambiguation issues of the parser by the hand(literally) of the player sounds like a dream.

1 Like

this is great and i’m already deep into shamelessly copying it.

i’m still confused about [put $Obj $Rel $Con]. now there’s just a link to [put $ in “something”], how are you planning on tying the “something” to an appropriate container/supporter?

and similarly, it seems like it gets weedy quickly with any action with 2 parameters.

Oh, for that, I’m relying on Dialog’s default (asking for object in $) behavior: the link executes the command PUT SANDWICH IN, and the game then asks “in what?” to get the second noun.

Actually, here, I should just post the code!

(grammar [put/lay/drop/hide/stash/stuff/insert [held] [in inside into through]] for [put $ #in])
(grammar [put/lay/drop [held] [on onto atop upon]] for [put $ #on])
(grammar [put/lay/drop/hide/stash/stuff [held] behind] for [put $ #behind])
(grammar [put/lay/drop/hide/stash/stuff [held] under] for [put $ #under])

(unlikely [put $Obj $])
	(current player $Actor)
	~($Obj has ancestor $Actor)

(perform [put $Obj $Rel])
	(uppercase) (name $Rel) what?
	(asking for object in [put $Obj $Rel []])

It works the same as the other actions that let you fill in a missing noun.

yeah, i thought that would be the case. so for disambiguation the player goes back to the parser?

i was wondering if there were a straightforward way to do it all with links. i suppose the inlinebar could redraw after (asking for…) in a sort of “disambiguation mode” and populate only with object links that are appropriate (nearby containers, supporters, etc.).

Yeah, clicking any link in the Å-machine automatically submits the command, so creating a command out of multiple links always requires multiple prompts.

Hmm, if there is a fish and a red fish in scope what happens when the player clicks on put fish in?

The fish gets chosen, thanks to (heads $)!

1 Like

But… but, both phrases have the same head noun fish. It won’t bother you to include red in the (heads $) as a linguist? :wink:

That’s true, but the purpose of (heads $) in Dialog is to avoid disambiguation problems like this that are unsolvable in Inform. I can live with a bit of linguistic imprecision to compensate!

(Though really the better solution is not to have a “fish” and a “red fish” in the same room at all.)

1 Like

so i’ve hit a snag with using links to talk to NPCs.

(link viable actions for $NPC)
	(animate $NPC)
	\[
	(link action [talk to $ about] with $NPC using [])
	\]

(unlikely [talk to $ about $])
 %% if this isn't here then the parser will disambiguate
 %% among ALL (topic *). i'm sure there's a reason for this that i don't understand.

(grammar [talk to [animate] about] for [talk to $ about])

(perform [talk to $NPC about])
	Talk to (the $NPC) about what?
	(asking for object in [talk to $NPC about []])

but this doesn’t work as expected. i only get

(I only understood you as far as wanting to talk to Abdullah about something.)

is there something uniqe or different about these actions?

(asking for ... in $) provides for objects and directions out-of-the-box. You need to extend it to cover topics.

Try adding these, see if it works:

(asking for topic in $)
(understand $Words as $Action)
	(implicit action is $Implicit)
	(if) (implicit action wants topic) (then)		%% added for topic
		*(understand $Words as topic $O)			%% coverage
	(elseif) (implicit action wants direction) (then)
		*(understand $Words as direction $O)
	(else)
		%% We don't know the correct policy, so fall back on 'non-all'.
		*(understand $Words as non-all object $O)
	(endif)
	(recover implicit action $Implicit $O into $Action) (just)	%% library override

%% This is new, all others are library overrides
(interface (asking for topic in $<Action))
(asking for topic in $Action)
	(now) (implicit action is $Action)
	(now) (implicit action wants topic)
	(now) ~(implicit action wants direction)
	(tick) (stop)

(asking for object in $Action)
	(now) (implicit action is $Action)
	(now) ~(implicit action wants direction)
	(now) ~(implicit action wants topic)		%% added for topic coverage
	(tick) (stop)

(asking for direction in $Action)
	(now) (implicit action is $Action)
	(now) (implicit action wants direction)
	(now) ~(implicit action wants topic)		%% added for topic coverage
	(tick) (stop)

(try-complex $ComplexAction) (just)				%% added (just) for override
	(if) ~(action $ComplexAction preserves the question) (then)
		(now) ~(implicit action is $)
		(now) ~(implicit action wants direction)
		(now) ~(implicit action wants topic)	%% added for topic coverage
	(endif)
	(if) ~(allowed action $ComplexAction) (then)
		(report disallowed action $ComplexAction)
		(tick) (stop)
	(elseif) ([] is one of $ComplexAction) (then)
		You're not aware of any such thing!
		(tick) (stop)
	(else)
		(strip decorations from $ComplexAction into $MultiAction)
		%% Now we have something like:
		%% [put [#marble1 #marble2 #marblefloor] #in #bowl]
		(if)
			*($Obj is one of $MultiAction)
			{
				(nonempty $Obj)
			(or)
				(object $Obj)
				~(direction $Obj)
				~(relation $Obj)
				~(room $Obj)
			}
		(then)
			(notice player's $Obj)
		(endif)
		(if) *($List is one of $MultiAction) (nonempty $List) (then)
			(exhaust) {
				*(regroup stripped action $MultiAction of
					$MultiAction into $Regrouped $Multi)
				%% Assuming the marbles are fungible, now we
				%% are backtracking over:
				%% [put [#marble1 #marble2] #in #bowl]
				%% [put #marblefloor #in #bowl]
				(if)
					~(empty $Multi)
				(then)
					%% If at least two multi-actions are
					%% implied, describe each step.
					(line)
					(if) (library links enabled) (then)
						(link) (The full $Multi)
					(else)
						(The full $Multi)
					(endif)
					:
				(endif)
				(try regrouped $Regrouped)
			}
		(else)
			%% Optimize the common case.
			(try regrouped $MultiAction)
		(endif)
	(endif)

Edit: Actually try this first before the rigmarole above:

(understand $Words as $Action)
	(implicit action is $Implicit)
	(if) (implicit action wants direction) (then)
		*(understand $Words as direction $O)
	(else)
		%% We don't know the correct policy, so fall back on 'non-all'.
			*(understand $Words as non-all object $O)
		(or)
			*(understand $Words as topic $O)		%% added for topic coverage
	(endif)
	(recover implicit action $Implicit $O into $Action) (just)	%% library override

Use asking for object for topics as well with this version.
Edit 2: And this might work even better(the override above is not ideal, might miss some possibilities with that). Just add this, no override necessary:

(understand $Words as $Action)
	(implicit action is $Implicit)
	(if) ~(implicit action wants direction) (then)
		*(understand $Words as topic $O)		%% added for topic coverage
	(endif)
	(recover implicit action $Implicit $O into $Action)

second one gives me “parameter two partially unbound” errors.

but first one works perfectly, thanks!

Unfortunately the first and second one are flawed, they will find only the first candidate if there are more than one, because of the (just) at the end of understand rule.

The third one should lead to correct disambiguation question. Does it get parameter unbound warning as well?

Edit: Of course it does, how about:

(understand $Words as $Action)
	(implicit action is $Implicit)
	(if) ~(implicit action wants direction) (then)
		*(understand $Words as topic $O)		%% added for topic coverage
		(recover implicit action $Implicit $O into $Action)
	(endif)