Conversation topic suggestions / code help

I’m looking to create a conversion topic suggestion mechanic. (I tried the Conversation Suggestions extension, but after wrestling with it for two hours I gave up.)

I’ve written the below. Adding new topics works great, but removing topics is not. I’m probably targeting the wrong value (“topic”). It keeps trying to remove the “books” even if you’re not asking about books. What have I done wrong?



The Library is a room.

Kevin is a person in the library.

[-----]

KevinTopics-List is a list of text that varies.

When play begins:
	now KevinTopics-List is { "Books, Library", "Door" } .

Understand "talk to kevin" as a mistake ("[if KevinTopics-List is not empty][italic type]The following is a list of suggestioned topics you could ask Kevin about:[paragraph break][KevinTopics-List].[roman type][paragraph break][otherwise if KevinTopics-List is empty][italic type]You have nothing to ask Kevin at the moment.[roman type]").

[-----]

The conversation selecting rules are a person based rulebook producing a table name. 

Instead of asking a person (called the interlocutor) about a topic:
	let the conversation table be the table name produced by the conversation selecting rules for the interlocutor;
	if the topic understood is a topic listed in the conversation table:
		say the response entry;
		say "[line break]".

A conversation selecting rule for Kevin:
	if the location is the Library:
		rule succeeds with result Table of LibraryKevin.
[-----]
		

To say AddKevinTopic:
	if the topic is "books":
		add "Desk" to KevinTopics-List.
		
To say RemoveKevinTopic:
	if the topic is "books":
		remove "books" from KevinTopics-List;
	otherwise if the topic is "library":
		remove "library" from KevinTopics-List;
	otherwise if the topic is "desk":
		remove "desk" from KevinTopics-List.

Table of LibraryKevin
Topic	Response
"books/magazines"	"'There are many here,' Kevin says.[AddKevinTopic]"
"library"	"'We're here,' Kevin says.[RemoveKevinTopic]"
"door"	"'It's open,' Kevin says."
"desk"	"'That's mine,' Kevin says.[RemoveKevinTopic]"

Test me with "talk to kevin / ask kevin about library / talk to kevin".

Okay, I see two things.

First, is “Books, Library” meant to be one topic with a comma in it, or two topics separated by commas? I’m guessing the latter.

The second thing is just the punctuation required to remove an item from a list.

Instead of
remove "books" from KevinTopics-List;
you have to use the curly brace notation:
remove { "Books" } from KevinTopics-List;
(see docs 21.5, Building Lists)

If you make Books its own topic then fix the removal line as above, your Test Me works. You should be able to proceed from there.

-Wade

3 Likes

Hm? Both work.

Do they? I didn’t know that. But none worked until we made the first change. That’s probably why I thought it didn’t work at all.

-Wade

To answer your first question, yes, they’re separate suggested topics (I don’t have serial comma loaded in this example).

However, remove { "library" } from KevinTopics-List; (curly brace or not), does not remove “library” from the list.

Welcome
An Interactive Fiction
Release 1 / Serial number 211123 / Inform 7 build 6M62 (I6/v6.33 lib 6/12N) SD

Library
You can see Kevin here.

>talk to kevin
The following is a list of suggestioned topics you could ask Kevin about:

Books, Library and Door.



>ask kevin about library
"We're here," Kevin says.

>talk to kevin
The following is a list of suggestioned topics you could ask Kevin about:

Books, Library and Door.

EDIT: I should say, however, that the addition of the curly braces has solved the issue of the first item in the list, in this case “books” being removed regardless of the actual topic. But now it’s not removing anything.

New example code with those curly braces:

The Library is a room.

Kevin is a person in the library.

[-----]

KevinTopics-List is a list of text that varies.

When play begins:
	now KevinTopics-List is { "Books, Library", "Door" } .

Understand "talk to kevin" as a mistake ("[if KevinTopics-List is not empty][italic type]The following is a list of suggestioned topics you could ask Kevin about:[paragraph break][KevinTopics-List].[roman type][paragraph break][otherwise if KevinTopics-List is empty][italic type]You have nothing to ask Kevin at the moment.[roman type]").

[-----]

The conversation selecting rules are a person based rulebook producing a table name. 

Instead of asking a person (called the interlocutor) about a topic:
	let the conversation table be the table name produced by the conversation selecting rules for the interlocutor;
	if the topic understood is a topic listed in the conversation table:
		say the response entry;
		say "[line break]".

A conversation selecting rule for Kevin:
	if the location is the Library:
		rule succeeds with result Table of LibraryKevin.
[-----]
		

To say AddKevinTopic:
	if the topic is "books":
		add "Desk" to KevinTopics-List.
		
To say RemoveKevinTopic:
	if the topic is "books":
		remove { "Books" } from KevinTopics-List;
	otherwise if the topic is "library":
		remove { "library" } from KevinTopics-List;
	otherwise if the topic is "desk":
		remove { "desk" } from KevinTopics-List.

Table of LibraryKevin
Topic	Response
"books"	"'There are many here,' Kevin says.[AddKevinTopic][RemoveKevinTopic]"
"library"	"'We're here,' Kevin says.[RemoveKevinTopic]"
"door"	"'It's open,' Kevin says."
"desk"	"'That's mine,' Kevin says.[RemoveKevinTopic]"

Test me with "talk to kevin / ask kevin about library / talk to kevin".

I see two problems:

You can’t use the topic in an expression. Topic is a kind, so the topic is always ambiguous. It doesn’t refer to the current topic. (Quick rule: if you write the before a kind name, you’re making a mistake.)

The second problem is that KevinTopics-List is a list of texts, but you’re trying to keep track of which topics have been used. Texts and topics are different kinds.

Sorry, trying to wrap my head around what to do. Then, why does it work for the expression AddKevinTopic? Adding to the list based on the topic is working great, it’s the removal of text (RemoveKevinTopic) from the list that isn’t.

The “if” is looking for the topic that was just used, but then the follow-up can frankly be whatever action we want to take? Right? Not seeing how the removal of a list item versus the topic causes conflict. In another example, we could do something wild like:

To say RemoveKevinTopic:
     if the topic is "library":
          move the player to the kitchen.

Bleh. Maybe adding to the list wasn’t working after all? Trying it now, it only works for the first “if” and anything below is ignored. Getting back to your point that “topic” is too generic.

EDIT:

I feel like a caveman who accidentally sparked a fire by banging rocks together, but I don’t know which rocks I used.

I created a value to store the topic understood and it’s truly working for adding new text to that list. It’s not removing text, however. The new code:



The Library is a room.

Kevin is a person in the library.

[-----]

KevinTopics-List is a list of text that varies.

When play begins:
	now KevinTopics-List is { "Books", "Library", "Door" } .

Understand "talk to kevin" as a mistake ("[if KevinTopics-List is not empty][italic type]The following is a list of suggestioned topics you could ask Kevin about:[paragraph break][KevinTopics-List].[roman type][paragraph break][otherwise if KevinTopics-List is empty][italic type]You have nothing to ask Kevin at the moment.[roman type]").

TopicStorage is text that varies.

[-----]

The conversation selecting rules are a person based rulebook producing a table name. 

Instead of asking a person (called the interlocutor) about a topic:
	let the conversation table be the table name produced by the conversation selecting rules for the interlocutor;
	if the topic understood is a topic listed in the conversation table:
		now TopicStorage is the topic understood;
		say the response entry;
		say "[line break]".

A conversation selecting rule for Kevin:
	if the location is the Library:
		rule succeeds with result Table of LibraryKevin.
[-----]

		

To say AddKevinTopic:
	if TopicStorage is "books":
		add { "Desk" } to KevinTopics-List;
	otherwise if TopicStorage is "door":
		add { "Key" } to KevinTopics-List.
		
To say RemoveKevinTopic:
	if TopicStorage is "books":
		remove { "Books" } from KevinTopics-List;
	otherwise if TopicStorage is "library":
		remove { "library" } from KevinTopics-List;
	otherwise if TopicStorage is "desk":
		remove { "desk" } from KevinTopics-List.

Table of LibraryKevin
Topic	Response
"books"	"'There are many here,' Kevin says.[AddKevinTopic][RemoveKevinTopic]"
"library"	"'We're here,' Kevin says.[RemoveKevinTopic]"
"door"	"'It's open,' Kevin says.[AddKevinTopic]"
"desk"	"'That's mine,' Kevin says.[RemoveKevinTopic]"

Test me with "talk to kevin / ask kevin about books / talk to kevin".

EDIT2:

And, eyyyyyy, the problem was getting back to severedhand’s note about the quotation marks in my list which I misunderstood. Fixed that and we’re now in action and it’s all working great.

Thank you both for helping me out and letting me talk through this.

Urgh! This was my initial mental response to the whole thing, but when I got off on the comma problem track, I let go of this idea because it appeared things had started working.

Anyway, glad you’re off and away now, Austin.

-Wade

2 Likes

This code is still going to get you into trouble.

The line:

now TopicStorage is the topic understood;

…implicitly converts a topic to a text. This will produce what the player typed, which is not going to be consistent.

Say you have a topic with variations:

"desk/table"	"'That's mine,' Kevin says."

The inputs “ASK KEVIN ABOUT DESK” and “ASK KEVIN ABOUT TABLE” will produce different TopicStorage texts, and that will mess up your tracking. In fact, so will capitalization differences.

Topics are for matching player input. Don’t put them in dynamic lists. If you want to keep track of topics this way, you should add a third column to the table containing texts, a kind-of-value, or an object to represent different topics. Then you can track that in your lists.

While I’m at it: you can still skip the braces in the “remove from list” lines.

Also, what about this?

say "[line break]".

Bad habit. Just write

say line break;

Yeah, I wasn’t as done as I thought. I had to modify this to be: now TopicStorage is topic understood in lower case;. There are also still issues if the player asks the NPC about the same topic multiple times; duplicate topics then get added to the list.

Being my first, big Inform project there are a lot of structural things I would have done differently at the beginning of the project that are too tangled to unwind now. But, I think I’m ok with that at this point. This was just supposed to be a quick, nice additive feature…

The use case for this whole thing is to suggest world-building/plot topics (largely proper nouns) to the player, not be a comprehensive topic-suggestion machine (the examples I’ve provided are quickly-built and not an actual slice of my game.).

For the poor soul who stumbles on this later thinking they might want to use it, here’s where my example ended:



The Library is a room.

Kevin is a person in the library.

[-----]

KevinTopics-List is a list of text that varies.

When play begins:
	now KevinTopics-List is { "books" } .

Understand "talk to kevin" as a mistake ("[if KevinTopics-List is not empty][italic type]The following is a list of suggestioned topics you could ask Kevin about:[paragraph break][KevinTopics-List].[roman type][paragraph break][otherwise if KevinTopics-List is empty][italic type]You have nothing to ask Kevin at the moment.[roman type]").

TopicStorage is text that varies.

[-----]

The conversation selecting rules are a person based rulebook producing a table name. 

Instead of asking a person (called the interlocutor) about a topic:
	let the conversation table be the table name produced by the conversation selecting rules for the interlocutor;
	if the topic understood is a topic listed in the conversation table:
		now TopicStorage is topic understood in lower case;
		say the response entry;
		say "[line break]".

A conversation selecting rule for Kevin:
	if the location is the Library:
		rule succeeds with result Table of LibraryKevin.
		
[-----]

		

To say AddKevinTopic:
	if TopicStorage is "books": [these entries need to be entirely in lower case]
		add { "library" } to KevinTopics-List;
	otherwise if TopicStorage is "library":
		add { "check out" } to KevinTopics-List.
		
To say RemoveKevinTopic:
	if TopicStorage is "books": [these entries need to be entirely in lower case]
		remove { "books" } from KevinTopics-List;
	otherwise if TopicStorage is "library":
		remove { "library" } from KevinTopics-List.

Table of LibraryKevin
Topic	Response
"books"	"'There are many here,' Kevin says.[AddKevinTopic][RemoveKevinTopic]"
"library"	"'We're here,' Kevin says.[AddKevinTopic][RemoveKevinTopic]"


Test me with "talk to kevin / ask kevin about books / talk to kevin / ask kevin about library / talk to kevin".

I’m not sure, but I think those lines are not totally equivalent. I have some kind of recollection where they behaved differently regarding substitution/substituted forms, and I had to use the first one to make something work as expected. But I can’t remember what exactly.

Obviously, most of the time it won’t matter, but I wouldn’t call it a bad habit; actually I prefer the first one style-wise. (Except if you had something else in mind?)

Also, in the original code, instead of

say the response entry;
say "[line break]".

I would write a single line:

say "[the response entry][line break]".

But well, maybe that’s just me, and as I said, it shouldn’t matter in that case.

2 Likes

At one point, I went through the enire source of Counterfeit Monkey and replaced all say "[line break]" and say "[N]" with say line break and say N, and similar. There were hundreds of those, and afterwards the game worked just as before, but became quite a bit faster.

1 Like

Related tangent on the substitutions angle:

In my games, I now use abbreviations for line break and paragraph break:

To say lb: say line break;
To say pb: say paragraph break;

This saves me centuries of keystrokes. Somewhere, it does change line-spacing behaviour a bit, but not in a way you notice if you’re doing it from the start, because when doing it, you’re accounting for that behaviour.

The way I found it changed behaviour was that when I returned to an older game where I hadn’t been doing this, I tried doing a mostly blanket find-and-replace to the new system and tons of the line-spacing in the game changed. So I left the old game how it was.

-Wade

I had to re-check this, of course. Between commit 341d6b6 and 475debe, I removed all I could find of these kinds of string conversions. So just now I compiled the game before and after, ran the full-playthrough scripts and compared the transcripts. No whitespace changes or other output changes.

However, looking at the actual changes, they were all of the kind let T be "[item]";let T be the printed name of item, and no actual [line break]. So that may be different.

EDIT 2: Of course I had to test this properly, so I did it again but replaced a lot (though not all) of the say "[line break]" and say "[paragraph break]" with say line break and say paragraph break, and the result was the same. No changes.

The game I sought to move to my new system was Six.

I’ve just isolated the game’s credit printout code using both modes. The attached sample (just paste its text into a blank project) shows doing it both ways and the line break changes that occur.

creditstext.txt (3.1 KB)

PS - I don’t want the credits code pasted into this topic as text at any point because then they’ll be google-able here forever :slight_smile:

-Wade

Right, the difference is that saying a string that ends with a period will automatically add a paragraph break (I think) line break, so say "Wade Clake."; say line break; will produce a paragraph break plus a line break two line breaks, while say "Wade Clarke.[line break]" will only produce one line break, because the string no longer ends with a period.

Nathanael’s Cookbook is the best reference I know on line break/paragraph break behavior. What we’re seeing in Wade’s example is covered here:

Nearly every time you invoke say "Something with a period just before a left bracket.[if true] This one too.[end if]"; Inform 7 will add an implicit [line break] just before the left bracket. […] These can also be suppressed by adding a space after the period, since it only checks if the period is immediately before the close quote or left bracket.

If you put spaces between the periods and the [lb]'s in the second version in Wade’s example, you’ll get something that looks the same as the first (but isn’t identical because it has spaces before the newlines). [line break] itself is apparently an exception to the “nearly every time” above; a period immediately before its left bracket results in just one newline and not two.

1 Like

Thanks, I edited my comment above. I guess there is some technical explanation for this inconsistency, where [line break] is converted behind the scenes to an actual \n control character or similar, while [end if] is not.