As well as making a map of your game, you can record the steps you took to solve it. IFM can then calculate a (fairly) optimal solution. This section is a guide to how to do it. Again, it's not a complete specification--see Section 5 for that.
The basic game-solving action is called a ``task'' in IFM-speak. To introduce a task, you use the task command, like this:
The game solver in IFM divides tasks into two fundamental types: ``safe'' and ``unsafe''. An unsafe task is one that, if done, might get you into a position where solving the game is impossible. The game solver avoids doing unsafe tasks until it really has to.
A lot of tasks require a previous task to be done first. To say this, you use the after clause:
...
task "Put the fluffy bunny in the incinerator" after press_switch;
task "Put the fluffy bunny in the incinerator" after last;
The after clause only says that a task will come after another--it doesn't specify how soon after. But in some situations it is essential that a task immediately follows a specific previous task, without deviation. You can use the task follow clause to specify this. For example:
task "Activate launch sequence" tag activate;
...
room "Rocket Cabin" ...;
task "Fasten seat belt" follow activate;
Of course, you can only have a single task in a follow clause--the immediately preceding task. It is an error for two or more tasks to try to immediately follow the same task.
For a lot of tasks, you need to have one or more items in your possession. You can indicate this by using the need clause, like this:
Note that you don't need to add tasks to get required items yourself--the game solver does that automatically. It knows it has to get all the items which appear in need clauses.
Sometimes a task need to be done before you can get an item. One way to indicate this is with the task get clause:
An alternate way to phrase this is with the item after clause, which says that the item can't be picked up until a specified task is done. This is a common combination in IFM:
item "lemonade" hidden after last;
In some circumstances, all the effects of doing a task occur before the task is done. If this happens, the task will be ignored. For example, if a task A gives an item, but that item is first given by task B, then task A will be ignored (provided it doesn't do anything else of importance).
If a task has no effect, it is not ignored--it's assumed that you've recorded that you did something but don't know why yet. Also, tasks which finish the game or score points are never ignored.
You can explicitly ignore a task using the ignore attribute. This is useful while you're solving the game (see Section 3.7) and when the task can be done by other tasks (see the next section).
You can arrange for a task to automatically do other tasks after it is done, using the do clause. For example:
task "Press airlock button" do open_airlock;
...
room "Outer Airlock";
task "Open airlock" tag open_airlock;
room "Inner Airlock" dir s after last;
The do clause causes any task to be done--even tasks that have prerequisites, and explicitly ignored ones. This is useful in that you can create special ignored tasks that can be done by any of a set of other tasks, whichever gets there first. The do clause is also recursive: a task can do another task which in turn does another, and so on.2
Note that any task which does an unsafe task in this way is itself marked unsafe.
Items can be split into two types: useful and useless. A useful item one that is needed by at least one task, or is required in order to be able to move somewhere; all other items are useless. The game solver will try to go and get all useful items, and will ignore the useless ones. It keeps track of the items it's carrying, and knows when a useful item is no longer needed.3 At that point, it will be dropped.
If the solver obtains a useless item (via a task get or give clause, or an item need clause) it will never drop it. This is just a default; you can change it by setting the variable keep_unused_items to zero. In that case, useless items will be dropped as soon as possible.4
If you want to make sure that an item is never dropped in any circumstance, you can mark it with the keep attribute. This is handy for items that act as general containers for other items.
Sometimes a useful item needs to be kept for longer than usual. In the hot coal example above, the tongs would be dropped as soon as the coal was picked up, leaving you with a burnt hand. What's needed here is to carry the tongs for as long as you have the coal. You can use the keep with clause to say that:
item "tongs" tag tongs keep with coal;
Finally, if a room has the attribute nodrop set, no items will be voluntarily dropped in that room. Any items which need to be dropped will then be dropped after the next task that happens in a room where dropping is allowed.
Sometimes, doing a task causes items to be destroyed. You can say that with the lose clause:
Incidentally, you can use the special tag it to refer to the last room, item or task tag name within a command. So the previous example could also have been written
As mentioned in Section 3.3.1, IFM knows when a useful item is no longer needed, and drops it automatically. But sometimes items need to be dropped temporarily, even though they're needed later. You can do that using the drop clause:
Sometimes items must be dropped in a different room to the one you're in. You can use the in clause to modify things:
task "Put laundry in chute" need laundry drop it in Bottom_of_Chute;
Normally, if an item is dropped but is needed again for some other task, there is nothing to stop the game solver picking it up again (provided there's a path to the room the item was dropped in). But sometimes you need to drop an item and not pick it up again until you've done something else. You can use the until clause to say that:
There are some situations where your movement is blocked if you are carrying particular items. You can use the leave attribute of rooms, links and joins to specify a list of items that must be left behind before using them. For example:
item "heavy boulder" tag boulder;
room "Top of Canyon" dir n go up leave boulder;
You can also say leave all, which means that you must leave all the items you're currently carrying, and leave all except, which omits certain items from being left behind.
When finding a solution, the game solver will carry items until it is forced to drop them. If the dropped items are needed later, the game solver will try to come back and get them. If it is trying to do a task which requires items, it will choose a route to get to the task room which doesn't involve dropping any of the needed items.
Note that the leave clause overrides the room nodrop attribute; items will be dropped even in those rooms.
Sometimes an item is required, or a task needs to be done, before movement in a certain direction is possible. To represent this, you can give need and after clauses to rooms, links and joins. For example:
task "Unlock the iron door" need rusty_key;
room "Crypt" dir s go down after last;
room "Dark Passage" dir e need candle;
In some cases, doing a task closes off a room, link or join so that it can't be used any more. You can use the before clause to indicate this:
room "Bank Entrance" tag Entrance dir e before trigger_alarm;
Sometimes in a game there is the situation where a path is closed off and, later on in the game, reopened again. A single link or join can't represent this. However, there's nothing to stop you from using two or more joins between the same rooms. If you mark them with the hidden attribute, they won't appear on the map either. For example, this line could be added to the previous example to provide an escape route:
There are several different ways of moving around in a game. The usual way is to say the direction you want to go in. Another way is to do something else which results in movement. A good example is the magic word XYZZY from Colossal Cave. It acts exactly like a movement command, in that you can use it again and again and it moves you somewhere predictable. The best way to represent this in IFM is to use a join to connect the two rooms, and specify the command used to do the movement via the cmd clause, like this:
Many games have some sort of scoring system, whereby you get points for doing various things. In IFM you can record this using the score clause, which can apply to rooms, items or tasks. It takes one integer argument--a score value. For rooms, it's the score you get when visiting it for the first time. For items, it's the score for first picking it up. For tasks, it's the score for doing that task. If an item has a score, but is given to the player via a task give clause, then its score is added to the score for that task instead.
Usually a game finishes when you complete some final task. You can indicate which task this is using the finish attribute. This attribute can attach to rooms, items or tasks, giving three different types of finish condition: entering a room, picking up an object or doing a task. If the game solver ever manages to do something which is flagged with the finish attribute, it considers the game solved and stops. Any extra things left to do will not be done, even if they score points.
Here's what the game solver does in order to come up with a solution to the game. First, extra internal tasks are generated. These are tasks to:
Then the task dependencies are calculated. A dependency is where one task must be done before another. The task dependencies are examined to see if there are any cycles--that is, chains of tasks where each one must be done before the next, and the last must be done before the first. If there are any, then the game is unsolvable, since none of the tasks in a cycle can be done.
If there are no cyclic dependencies, the task list is ``traversed'' to find a sequence which solves the game while satisfying the task dependencies. The start room is the room which was first mentioned in the input (but this can be changed--see Section 5). While there are tasks left in the task list, the following steps are performed:
There will be some situations (quite a few, actually) where the game solver doesn't do things the way you want it to. This section gives a few tips, and some new keywords, for modifying things.
Firstly, the game solver is completely paranoid. It has to be, because it doesn't do any lookahead past the current task. It won't do anything unsafe (e.g. go to a room to do a task when there's no immediate return journey) unless there's nothing safe left to do. It will quite happily plod halfway across the map to pick something up rather than do something a bit scary in the next room.
However, you can reassure it with the task safe attribute. Adding this to a task tells the solver that this task is safe, regardless of what it thinks. So if you know that a one-way trip can eventually be returned from, by doing other tasks, you can stop the solver from avoiding it. But bear in mind that by doing this you are taking full responsibility if the solver gets stuck.
If you want to be seriously reckless, you can set the variable all_tasks_safe to a nonzero value. Then, all tasks will be considered safe.
Another thing the solver doesn't know about is how easy or difficult it is to get from place to place on the map. Suppose you're in a game which is on two levels separated by a tiresome set of access doors with ID cards. The connection between the levels may only be two rooms on the map, but it's a lot more in terms of typing. You can avoid unnecessary trips through these doors by artificially changing the ``length'' of the connection between levels, by using the length attribute of links and joins:
room "Level B" tag LB dir e length 50;
There may be times when you want a map connection to appear on the map, but not be used in solving the game--for example, it may be certain death to go that way. You can use the nopath attribute of rooms, links and joins to indicate this. It doesn't affect map output in any way.
Another use for this attribute is to force the game solver to do things in a different order. This might be preferable to adding fake task dependencies.
Sometimes it's useful to be able to ignore certain parts of the solution--for example, if you want to generate a sequence of game commands that get you to a particular position as quickly as possible. To that end, you can mark tasks and items with the ignore attribute. An ignored task will never be attempted, and an ignored item will never be picked up. This means that anything dependent on those tasks or items will not be done either. The game will very probably be unsolvable as a result, unless you've ignored an unused item, or ignored a task that's done via a do clause.
It's probably best to keep all your ``game tweaks'' together, separate from the ``pure'' game, and commented appropriately. You can do this by using commands which just modify existing objects, instead of creating new ones, by referring to their tags. As an example, suppose you have the following situation:
room "Bottom of Chute" dir s go down oneway;
task "Do something weird" tag weird_task;
...
task weird_task need burger safe;
This way of modifying previous objects applies all types of object, even links and joins--these can be tagged too, in the normal way. The single exception is the implicit link created by the room dir clause. These links automatically get tagged when the room does, and with the same name. So the two-level example above could be split into:
room "Level B" tag LB dir e;
...
# Stop gratuitous travel between levels.
link LB length 50;
Finally, you can gain an insight into what the game solver's up to by setting the solver_messages variable (either in one of the input files, or via the -set command-line option). This produces reams of output giving details of the game solver's thoughts before it does anything. It's supposed to be self-explanatory, but my view is slightly biased. Detailed documentation may follow (a) if enough people ask for it, and (b) if I ever get around to it.
Given the wild imaginations of today's IF authors, there are bound to be some game solving situations that can't easily be dealt with using IFM. Some of the things that IFM ignores are: