Hint Systems Good and Bad

Heh, I realise what I actually wanted was the word ‘dream’. Zed’s Dream System.

-Wade

2 Likes

What would be the best way to implement the fantasy system everyone is talking about? It looks very much in what I am trying to do in my game, but I have some challenges in not hinting at things the player is not aware of yet. E.g. hinting where to find a key would only make sense after the player found (and attempted to pass through or open) the locked door. I can implement that by adding a lot of state checking but I feel there must be an easier way?

Also, I have multiple challenges the player can work on (trying to avoid a linear scenario to allow the player more freedom), and then the question becomes: which hint to offer? I can imagine if you are in a room related to a puzzle it would make sense to offer a hint for that puzzle, but what if the player is somewhere else? Direct them to a list of ‘puzzle rooms’ to pick from and take it from there?

I do want the hinting to be more ‘natural’ and use an NPC to provide them (Not sure what would be the best way to divulge the nuggets of wisdom.), something along the lines of:

> hint
You shrug your shoulders, unable to think of what to do next.
Alice looks at you and sighs. "Well you got the key already in your backpack, just get it out and unlock that door!"

Would that make sense? Or would “talk to alice” be better here (I rather like the briefness of ‘hint’ but it feels like breaking mimesis to me).

1 Like

I don’t know what the rest of your conversation system looks like, but what I usually do (in games in general, not just IF) is to have conversation nodes that get locked and unlocked by puzzle progress (first time you see the mouse trap it unlocks Alice’s dialog about building a better mousetrap, and then once the player has solved the puzzle the conversation node disappears again).

As for how to implement a more general relational graph of puzzles, it depends a bit on what kind of instrumentation you’ve built into the rest of your game. In my case I’m using TADS3, so a lot of the goal-oriented stuff is packed into different classes of Agenda, so vertices of the puzzle graph map to agendas. Figuring out what the next step in solving the puzzle starts out at looking what Agenda is currently active (if you’re not familiar, this is handled by the interpreter, which steps through agendas calling a method on each to see if it reports as ready, and then stops at the first active one). From there it’s literally just using Dijkstra (or some equivalent pathfinding algorithm) to find the path between the current Agenda and the eventual solution.

The Agendas are all goal oriented (“go to this location”, or “take this object”) sort of things and so “solving” the individual steps of the puzzle happens there, and the graph is just the thing that strings them together into the overall solution.

I’ve described everything in terms of Agendas here, but they’re basically just a simple builtin behavioral FSM provided by TADS3/adv3. The basic “gimmick” or whatever is just providing a hook to call a widget on an actor/mob/whatever every turn, combined with a callback/method/whatever to indicate whether or not a given specific widget should be the one used this turn (basically a way of resolving priorities and checking prerequisites—a “go to some location” widget can’t function if you haven’t given it a location to go to, for example), a callback/method/whatever that does whatever it is the widget does (call the pathfinder to find the next step to take along the path to the destination and then make that move), and (probably, if it’s not part of the last step) a callback/method/whatever to check if the widget has accomplished whatever it’s trying to do (check to see if the mob is now in the destination).

If that answers your question. There are actually a lot of different ways of handling this sort of thing, and what makes the most sense really depends on what kind of instrumentation the rest of the “stuff” in the game has—what kind of provisions it has for scheduling tasks, scripting movement and other actions, and so on.

It also sometimes makes sense to encapsulate all of the logic for complicated puzzles into their own class/object that has an internal graph/FSM/grammar/whatever for keeping track of progress of quests or whatever. This usually makes sense if you need to provide a lot of reporting/UI updates/whatever for puzzle progress (think of something like an RPG quest with waypoints and so on) but it might be overkill for most IF puzzles, which tend to be more or less opaque to the player (no “quests” tab in the main menu where players can toggle which objectives they want displayed on the minimap and that kind of thing).

1 Like

well, kinda like what I get into in my followup:

the best way would probably be to design a whole game engine with facilitating this in mind, except, of course, for the part where creating your own game engine is always the worst choice.

There’s no way around that you’ll be doing a lot of state-checking; the trick would be arranging the info in a way that made the relevant state-checking a cheap and easy operation. If I were tackling it, I’d start with doing real homework reading up on constraint resolution engines and data structures/algorithms for graphs (which I feel confident would be central to approaching the problem, though I’d be open to my homework convincing me of something different).

3 Likes

All my games have a hint system like this, but which is implemented pretty poorly (so not actually like this).

An excerpt:

Morganhinting is an action out of world. Understand "hint morgan" as morganhinting.

Carry out morganhinting:
	if the chief engineer is in mechanical marvels:
		say "You're not ready for this hint yet. You need to solve the problem of the clogged pipe first. Type HINT PIPE for a hint with that problem.";
	if the mantle is enclosed by the player:
		say "You have already done everything you need to with Morgan the Mechanical. Nice work!";
	otherwise if the mantle is in Morgan's Tent:
		say "Morgan dropped her mantle in her tent. Don't forget to pick it up!";
	otherwise if threecarded is true:
		say "All you need to do is finish Morgan's trick one last time. For explicit instructions on finishing the trick type HINT TRICK.";
	otherwise if morgan's chest is not nowhere:
		say "You found the chest, you just have to TAKE it and bring it back to Morgan.";
	otherwise if morgan's chest is known:
		say "You haven't found Morgan's chest yet. ";
		if the creaky key is nowhere:
			say "Remember the slippers you were given? You must finish the tango quest to progress. Type HINT DANCE for hints with that puzzle.";
		otherwise:
			say "Remember how Morgan said the slippers would help you, and they led to you getting the creaky key? You need to finish the creaky house. Type HINT CREAKY for hints with the creaky house.";
	otherwise:
		if morgan's tent is visited:
			say "You have to complete Morgan's card trick to progress. If you want an explicit solution to performing the trick, type HINT TRICK.";
		otherwise:
			say "You'll need to visit Morgan's tent, south of the Bitter End, to start this puzzle.";

Edit: Hmmm after re-reading the discussion, I realize now that this is one of those bad hint systems that recreates the whole state structure!

4 Likes

Yeah. This is a fairly common (or at least not uncommon) way to implement roguelikes/roguelites, and procgen-heavy games in general. Most generic game engines/frameworks that are implemented in/as a general-purpose programming language make this easier than most IF frameworks, because IF frameworks make a bunch of assumptions about what kinds of games are going to be implemented in them. TADS3 is the one I’m most familiar with, and even though it’s more C-like than, for example, I7, reflection is kinda rudimentary, object lifecycle is really only well-defined at init time, and the terp/parser really expects all objects to be declared statically in the source (dynamic creation of objects is supported but can be tricky, and object destruction is delete references and hope the garbage collector is having a good day).

That all said, I’ve got some procgen stuff in the T3 game I’m currently working on, and it’s all relational graphs that get mapped to T3 rooms/objects/actors under the hood. The main thing T3 does to help here is having a fairly robust notion of multiple inheritance, and so you can glom procgen logic onto a lot of existing stuff (like Room) by mixin classes, subclassing, and so on.

2 Likes

Well, programatically it’s far from Zed’s vision, but in output, without such a system, this is about as good as it gets, isn’t it? I mean, it’s doing what you want. It’s giving the hints you think are appropriate with your knowledge of the gates in the game.

Zed’s system could give extra hints inbetween, though some of them would probably not be neccessary. On the other hand, being completist, it’s likely to deliver the odd hint that the majority of players never need, but one player out there does at a particular moment.

The thing is, until such a system is built, doing what you’ve done is not above-average maintenance and is a very good system.

-Wade

6 Likes

Warning! Long post alert!

A basic but extensible hint system that might have some of the

  1. Figure out the steps/dependencies in solving a puzzle. Some of @zed’s ideas would help automate this step.

  2. Write out the steps/dependencies in solving a puzzle.

  3. Check intermediate requirements: players needing to be in a particular place counts as a step, as does having a particular item in the player’s possession or finding out a certain piece of information from an NPC. Some games will have other requirements as well, which you should check.

  4. Decide what the first step is in the puzzle (this may be, and often is, as simple as identifying whether the player has entered a particular room).

  5. Repeat for all puzzles in the game.

  6. Check for places where puzzle steps/dependencies connect onto each other.

  7. Make sure there is also an underlying puzzle chain that covers all parts of the game not covered by special cases or (optionally) other puzzle chains. This should go from the first point to the last point where a general hint system makes sense.

  8. Code the puzzles.

  9. Check which steps naturally got a variable assigned to them; use that variable for the applicable hint conditional statement. (If you did the perfect ideal case scenario and your initial plan identified all steps pre-coding, it’s possible this will be a very short check. Do it anyway).

  10. Assign a variable to anything that didn’t naturally get one.

  11. Set out a distinct area for the hint system.
    [If I was trying to do this in Ren’Py, I would create a separate file for the hint system, so it didn’t get confused with anything else.]

  12. Use if-elif-else chains (or your programming language’s equivalent) for each puzzle, using the variables. The first “if” would be if the variable for completing the puzzle was TRUE and the “else” would be if the variable for finding the first part of the puzzle was FALSE.
    [Example: in Ren’Py, a three-stage puzzle (pick up plate, go to adjacent room, serve food) would look like this:

if servedfood == true:
pass
elif takefood == true and location == banquet:
“Now, serve the food.”
elif takefood == true:
“Please go to the banquet hall with this delicious plate of food - and don’t try eating it or else the cook will be furious.”
else:
“The food needs to be picked up.”

Note that you are not limited to true/false unless your programming language imposes that restriction, which is particularly useful for things like location (where the player may be in any of a large number of rooms, depending on the size of the game.]

  1. Set it so that if the initial “IF” is TRUE, the code skips to the next puzzle dependency set.
    [In Ren’Py, this would be done by the instruction “pass” in my previous example.]

  2. If any item on the list is also a variable that appears in another puzzle, then how this is handled depends on the nature of the puzzles:

If one puzzle must be completed before the other one can be finished, the first one should continue giving clues as usual. The other should have an elif above it with the variable for completing the first puzzle as the condition. That one has the clue; the original item would have an instruction to skip to the next puzzle set.

If one of the puzzles is in the underlying puzzle chain, that cannot be skipped; spell out that one, and if the other puzzle can’t be done until the underlying puzzle chain one is complete, put the skip instruction in the other one.

If the two puzzles can be done in either order, then check with the player which of them requires the hint. This requires a lot of author discretion to avoid giving the game away with either puzzle, especially if the player is not obliged to know multiple puzzles are available at that point (e.g. because one of them is an Easter egg and the other is an openly-available side quest).

  1. Sequence the puzzles. The one that covers the underlying puzzle chain must go last (since the hints for that one cover all scenarios not otherwise covered in your game).

Other essential puzzles go first, in the order they must be completed.

If several essential puzzles can be completed in any order, then you can put them in any order - but still make sure they go after any prerequisite puzzles (that are not in the underlying puzzle chain) and before anything which depends on it in turn.

Optional puzzles go between essential puzzles and the underlying structure, even if they depend on the underlying puzzle structure to be possible. Care must be taken in this situation to make sure the player gets the hint to the correct puzzle.

The underlying puzzle structure is there as a catch net for any situation where the player has no puzzles they are partway through, and should have hints on how to get to the essential things. Because it’s the catch-net, it’s OK to have the essential puzzle that ends everything here - provided that it is the puzzle. If you try putting two simultaneous puzzles here, you run the risk of the program (or the programmer) getting confused, which is the way to hint systems throwing their digital hands in the air and declaring they cannot help.

[So the structure may go like this - adapt as appropriate to your game:

Tutorial of basic actions puzzle (start point)
Tutorial of conversation puzzle (requires previous tutorial)
Tutorial of game’s unique mechanic puzzle (requires both previous tutorials)

Essential puzzle 1 (that depends on completing all tutorial puzzles)
Essential puzzle 2a
Essential puzzle 2b (which branches off 2a but could be completed before 2a depending on player preference)

Essential puzzle 3a (which requires Essential puzzle 2a to complete)
Essential puzzle 3b (which you can complete before or after 3a, and likewise requires Essential puzzle 2a)
Essential puzzle 3c (which requires Essential puzzle 2b and can be completed before or after 3a and 3b)

Essential puzzle 4a (which requires all of Essential Puzzles 3a-3c)
Essential puzzle 4b (which requires Essential Puzzle 4b and opens up the Final Setpiece Puzzle)

Optional puzzle 1 (which can be done at any time)
Optional puzzle 2 (which depends on completing the Tutorials)
Optional puzzle 3 (which depends on Optional Puzzle 1 and Essential Puzzle 2a)
Optional puzzle 4 (
Optional puzzle 5 (which can only be completed during the Final Setpiece Puzzle sequence)

Easter egg (hidden in Essential Puzzle 3b)

Underlying puzzle structure (including Final Setpiece Puzzle)]

  1. If doing a gradual hint system, nest an if-elif-else statement within each hint you want to make gradual. (It might not make much sense if it’s the initial “go to such-and-such a place”, but if the hint is potentially spoilery, that should be the first thing to make gradual). Note that you may iterate this to taste and you can continue doing this through testing, provided you make sure all variables associated with the gradual hints are properly coded (in some programming languages, adding new variables makes saved games invalid or unstable).

[For example, in Ren’Py, for gradual hinting on the :

elif takefood == true:
if servedfoodhint3 == 0:
“The kitchen is full of wonderful smells. The aroma of roast parsnips from the serving table, with notes of rosemary, is especially notable.”
elif servedfoodhint3 == 1:
“Someone is definitely looking forward to their rosemary-infused roast parsnips.”
elif servedfoodhint3 == 2:
“You fondly recall the day you were made a page in Count Guthrin’s household. In return for board, lodgings, a good education and respectable connections, you promised to help with such tasks as delivering messages, tidying up clothes and banquet service.”
elif servedfoodhint3 == 3:
“Nearby, another page effortlessly lifts a trencher of meat - pheasant in wine, seasoned with ginger - and disappears into the hubbub outside. The parsnips you are carrying are on a slightly smaller trencher. They look too good to eat.”
elif servedfoodhint3 == 4:
“The sous chef snaps ‘The food is for you to serve, not eat or stare!’”
elif servedfoodhint3 == 5:
“The apprentice cook recognises you.”
apprenticecook "‘Please go to the banquet hall with this delicious plate of food - and don’t try eating it or else the sous chef will be even more furious.’
souschef “HURRY UP! The parsnips for the Count will be cold.”
else:
souschef “Get that food to the banquet hall. Now!”
apprenticecook “He doesn’t mean to snap. But please do as he says.”]

Yes, it recreates the state structure, but it doesn’t require me to read at least two books on Python graphing (one each in Pythons 2 and 3, due to Ren’Py changing Python version during development) or experiment to figure out how to get it to express in Ren’Py.

6 Likes

My game does have a very basic state tracking system for what I guess could be called hints. I treat it more as a goal tracking action. Certain actions change a variable, which corresponds to text in a table, which the player can view with a command. It wound up easy to update and extend, but hard to read. I really would have liked something more flexible and readable. Note this was my first effort to use table data in Inform (nearly a year ago). I’d do things differently today.

Carry out hassling:
	say "[goalstring in row G of table of goals][line break]"

There are 30 rows right now for a geographically small game with only a handful of puzzles. These aren’t hints, either, just player objectives. It’s easy to maintain, but things get a little weird when there are multiple objectives. For instance, a point where multiple independent objectives have equal importance really highlights the limitations of my approach. I lack the capability to write something like Zed’s concept, but I will try to do better in my second game (provided the first game doesn’t get me run out of town).

I think a goal-tracking command is a nice courtesy and will want to continue offering one.

6 Likes

I’m doing pretty much exactly the same as you in my game (as goals) with the possible difference that they get moved to a completed section once done (just directly below the to-dos) and are re-described there in completed terms.

I suppose if you have too many goals of equal value, and aren’t content to just list them in what you might call chronological order (game-encountered order), you could add priority groupings. You might consider this overkill for your game – or you might not – but you could subgroup them with headings that show that everything below a heading is of equal weight/priority. There’s still a lot of framing, or creative, room to move in deciding what headings you might use.

I might need something like this myself later. But I’m not even up to the ‘wacky’ stuff yet.

I find re-presenting the goals in the DONE section is strangely tricky. Checking for multiple incomplete states versus some sign that a whole task is done are not equivalent in programming logic (they probably would be in ZED’S DREAM SYSTEM ™ but, yknow, with a bunch of if statements, they aren’t - or it’s only after a lot of programming you realise all the constraints.)

-Wade

4 Likes

Great ideas in this thread. I am seriously studying both hints and NPC conversation for incorporation into a future game. I brute forced a hint system in my EctComp entry. I tried to make it progressive but it was pretty ham handed.

3 Likes

In I7, I would use relations: make a “goal” kind, a relation for goals depending on each other, and a separate relation for goals requiring things. Each goal would have its own “Definition: [this goal] is complete if…”, and an appropriate printed name.

To give a hint, you’d first select an appropriate puzzle (an incomplete goal which doesn’t depend on any incomplete goals), then check what it requires. If it requires something they’ve seen that’s not nearby, tell them about it (“take another look at that [thing] in [place]”); otherwise, direct them toward it (“you haven’t explored [direction] from [place]”).

Maybe have another text property (“final hint”?) which is printed if they have all the things it depends on but haven’t solved the puzzle yet.

The hard part, of course, is making sure your dependency chart is accurate.

3 Likes

Well, in something like an IF title you can probably take care of that by just scripting the steps.

So in your example, in addition to your generic Goal class (or whatever you want to call it), you make subclasses for different flavors of goal you have in your puzzles. So you’d almost certainly want something like a TakeGoal and a GoToGoal to implement a simple “find the foozle” quest.

GoToGoal takes an argument/property/whatever that defines what it’s after. It has a method/property/whatever that’s set to true if and only if the player (or whoever is trying to complete the quest) is in the presence of the “target”. To hint using GoToGoal you have a method or whatever that spits out “[Questing person] has to go to [outermost room containing the quest target]”. To script it, GoToGoal has a method (or whatever) that calls the pathfinder to find the next step the questing actor needs to take in order to move toward the target room and (assuming it’s possible) take the step. When the goal is reached, the hint/script method is a NOP (taking no action), and the next Goal/whatever is tested.

In this case TakeGoal or whatever tests if the questing actor already has the target object. If so, done. If not, it checks the corresponding GoToGoal is completed. If not, it does nothing (because GoToGoal is still doing its thing). If it is, then TakeGoal either gives you “[Questing person] needs to take the [target]” if hinting or executes >TAKE [target] if scripting.

And so on.

In simple cases like this “quests” can be assembled in simple one dimensional array/list/stack/whatever data structures, but (as was discussed earlier in the thread) you can also string things together in relational graphs. In that case, instead of simply sequentially testing each step in the “quest chain” you can use literally the same sort of pathfinding logic used for room-based navigation (e.g. Dijkstra) to figure out how to traverse the “quest” graph.

Assuming you don’t just screw up implementing the parallel hinting/scripting for each “step”, then you can verify the integrity of your puzzle dependencies by scripting a walkthrough of the puzzle.

3 Likes