cfad47cfa3/t3compiler/tads3/lib/adv3/hintsys.t

4b825dc642cb6eb9a060e54bf8d69288fbee4904cfad47cfa334b206c65f22086bcc5d63e6f70944
1
#charset "us-ascii"
2
3
/* 
4
 *   Copyright (c) 2000, 2006 by Michael J. Roberts.  All Rights Reserved. 
5
 *   
6
 *   TADS 3 Library - Hint System
7
 *   
8
 *   This module provides a hint system framework.  Games can use this
9
 *   framework to define context-sensitive hints for players.
10
 *   
11
 *   This module depends on the menus module to display the user interface.
12
 */
13
14
/* include the library header */
15
#include "adv3.h"
16
17
18
/* ------------------------------------------------------------------------ */
19
/*
20
 *   We refer to some properties defined primarily in score.t - that's an
21
 *   optional module, though, so make sure the compiler has heard of these. 
22
 */
23
property scoreCount;
24
25
26
/* ------------------------------------------------------------------------ */
27
/*
28
 *   A basic hint menu object.  This is an abstract base class that
29
 *   encapsulates some behavior common to different hint menu classes.  
30
 */
31
class HintMenuObject: object
32
    /*
33
     *   The topic order.  When we're about to show a list of open topics,
34
     *   we'll sort the list in ascending order of this property, then in
35
     *   ascending order of title.  By default, we set this order value to
36
     *   1000; if individual goals don't override this, then they'll
37
     *   simply be sorted lexically by topic name.  This can be used if
38
     *   there's some basis other than alphabetical order for sorting the
39
     *   list.  
40
     */
41
    topicOrder = 1000
42
43
    /*
44
     *   Compare this goal to another, for the purposes of sorting a list
45
     *   of topics.  Returns a positive number if this goal sorts after
46
     *   the other one, a negative number if this goal sorts before the
47
     *   other one, 0 if the relative order is arbitrary.
48
     *   
49
     *   By default, we'll sort by topicOrder if the topicOrder values are
50
     *   different, otherwise alphabetically by title.  
51
     */
52
    compareForTopicSort(other)
53
    {
54
        /* if the topicOrder values are different, sort by topicOrder */
55
        if (topicOrder != other.topicOrder)
56
            return topicOrder - other.topicOrder;
57
58
        /* the topicOrder values are the same, so sort by title */
59
        if (title > other.title)
60
            return 1;
61
        else if (title < other.title)
62
            return -1;
63
        else
64
            return 0;
65
    }
66
;
67
68
/*
69
 *   A Goal represents an open task: something that the player is trying
70
 *   to achieve.  A Goal is an abstract object, not part of the simulated
71
 *   world of the game.
72
 *   
73
 *   Each goal is associated with a hint topic (usually shown as a
74
 *   question, such as "How do I get past the guard?") and an ordered list
75
 *   of hints.  The hints are usually ordered from most general to most
76
 *   specific.  The idea is to let the player control how big a hint they
77
 *   get; we start with a small nudge and work towards giving away the
78
 *   puzzle completely, so the player can stop as soon as they see
79
 *   something that helps.
80
 *   
81
 *   At any given time, a goal can be in one of three states:
82
 *   
83
 *   - Open: this means that the player is (or ought to be) aware of the
84
 *   goal, but the goal hasn't yet been achieved.  Determining this
85
 *   awareness is up to the goal.  In some cases, a goal is opened as soon
86
 *   as the player has seen a particular object or entered a particular
87
 *   area; in other cases, a goal might be opened by a scripted event,
88
 *   such as a speech by an NPC telling the player they have to accomplish
89
 *   something.  A goal could even be opened by viewing a hint for another
90
 *   goal, because that hint could explain a gating goal that the player
91
 *   might not otherwise been able to know about.
92
 *   
93
 *   - Undiscovered: this means that the player doesn't yet have any
94
 *   reason to know about the goal.
95
 *   
96
 *   - Closed: this means that the player has accomplished the goal, or in
97
 *   some cases that the goal has become irrelevant. 
98
 *   
99
 *   The hint system only shows goals that are Open.  We don't show Closed
100
 *   goals because the player presumably has no need of them any longer;
101
 *   we don't show Undiscovered goals to avoid giving away developments
102
 *   later in the game before they become relevant.  
103
 */
104
enum OpenGoal, ClosedGoal, UndiscoveredGoal;
105
class Goal: MenuTopicItem, HintMenuObject
106
    /*
107
     *   The topic question associated with the goal.  The hint system
108
     *   shows a list of the topics for the goals that are currently open,
109
     *   so that the player can decide what area they want help on.  
110
     */
111
    title = ''
112
113
    /*
114
     *   Our parent menu - this is usually a HintMenu object.  In very
115
     *   simple hint systems, this could simply be a top-level hint menu
116
     *   container; more typically, the hint system will be structured
117
     *   into a menu tree that organizes the hint topics into several
118
     *   different submenus, for easier navigatino.  
119
     */
120
    location = nil
121
122
    /*
123
     *   The list of hints for this topic.  This should be ordered from
124
     *   most general to most specific; we offer the hints in the order
125
     *   they appear in this list, so the earlier hints should give away
126
     *   as little as possible, while the later hints should get
127
     *   progressively closer to just outright giving away the answer.
128
     *   
129
     *   Each entry in the list can be a simple (single-quoted) string, or
130
     *   it can be a Hint object.  In most cases, a string will do.  A
131
     *   Hint object is only needed when displaying the hint has some side
132
     *   effect, such as opening a new Goal.  
133
     */
134
    menuContents = []
135
136
    /*
137
     *   An optional object that, when seen by the player character, opens
138
     *   this goal.  It's often convenient to declare a goal open as soon
139
     *   as the player enters a particular area or has encountered a
140
     *   particular object.  For such cases, simply set this property to
141
     *   the room or object that opens the goal, and we'll automatically
142
     *   mark the goal as Open the next time the player asks for a hint
143
     *   after seeing the referenced object.  
144
     */
145
    openWhenSeen = nil
146
147
    /*
148
     *   An option object that, when seen by the player character, closes
149
     *   this goal.  Many goals will be things like "how do I find the
150
     *   X?", in which case it's nice to close the goal when the X is
151
     *   found. 
152
     */
153
    closeWhenSeen = nil
154
155
    /* 
156
     *   this is like openWhenSeen, but opens the topic when the given
157
     *   object is described (with EXAMINE) 
158
     */
159
    openWhenDescribed = nil
160
161
    /* close the goal when the given object is described */
162
    closeWhenDescribed = nil
163
164
    /*
165
     *   An optional Achievement object that opens this goal.  This goal
166
     *   will be opened automatically once the goal is achieved, if the
167
     *   goal was previously undiscovered.  This makes it easy to set up a
168
     *   hint topic that becomes available after a particular puzzle is
169
     *   solved, which is useful when a new puzzle only becomes known to
170
     *   the player after a gating puzzle has been solved.  
171
     */
172
    openWhenAchieved = nil
173
174
    /*
175
     *   An optional Achievement object that closes this goal.  Once the
176
     *   achievement is completed, this goal's state will automatically be
177
     *   set to Closed.  This makes it easy to associate the goal with a
178
     *   puzzle: once the puzzle is solved, there's no need to show hints
179
     *   for the goal any more.  
180
     */
181
    closeWhenAchieved = nil
182
183
    /*
184
     *   An optional Topic or Thing that opens this goal when the object
185
     *   becomes "known" to the player character.  This will open the goal
186
     *   as soon as gPlayerChar.knowsAbout(openWhenKnown) returns true.
187
     *   This makes it easy to open a goal as soon as the player comes
188
     *   across some information in the game.  
189
     */
190
    openWhenKnown = nil
191
192
    /* an optional Topic or Thing that closes this goal when known */
193
    closeWhenKnown = nil
194
195
    /*
196
     *   An optional <.reveal> tag name that opens this goal.  If this is
197
     *   set to a non-nil string, we'll automatically open this goal when
198
     *   the tag has been revealed via <.reveal> (or gReveal()). 
199
     */
200
    openWhenRevealed = nil
201
202
    /* an optional <.reveal> tag that closes this goal when revealed */
203
    closeWhenRevealed = nil
204
205
    /*
206
     *   An optional arbitrary check that opens the goal.  If this returns
207
     *   true, we'll open the goal.  This check is made in addition to the
208
     *   other checks (openWhenSeen, openWhenDescribed, etc).  This can be
209
     *   used for any custom check that doesn't fit into one of the
210
     *   standard openWhenXxx properties.  
211
     */
212
    openWhenTrue = nil
213
214
    /* an optional general-purpose check that closes the goal */
215
    closeWhenTrue = nil
216
217
    /*
218
     *   Determine if there's any condition that should open this goal.
219
     *   This checks openWhenSeen, openWhenDescribed, and all of the other
220
     *   openWhenXxx conditions; if any of these return true, then we'll
221
     *   return true.
222
     *   
223
     *   Note that this should generally NOT be overridden in individual
224
     *   instances; normally, instances would define openWhenTrue instead.
225
     *   However, some games might find that they use the same special
226
     *   condition over and over in many goals, often enough to warrant
227
     *   adding a new openWhenXxx property to Goal.  In these cases, you
228
     *   can use 'modify Goal' to override openWhen to add the new
229
     *   condition: simply define openWhen as (inherited || newCondition),
230
     *   where 'newCondition' is the new special condition you want to
231
     *   add.  
232
     */
233
    openWhen = (
234
        (openWhenSeen != nil && gPlayerChar.hasSeen(openWhenSeen))
235
        || (openWhenDescribed != nil && openWhenDescribed.described)
236
        || (openWhenAchieved != nil && openWhenAchieved.scoreCount != 0)
237
        || (openWhenKnown != nil && gPlayerChar.knowsAbout(openWhenKnown))
238
        || (openWhenRevealed != nil && gRevealed(openWhenRevealed))
239
        || openWhenTrue)
240
241
    /*
242
     *   Determine if there's any condition that should close this goal.
243
     *   We'll check closeWhenSeen, closeWhenDescribed, and all of the
244
     *   other closeWhenXxx conditions; if any of these return true, then
245
     *   we'll return true. 
246
     */
247
    closeWhen = (
248
        (closeWhenSeen != nil && gPlayerChar.hasSeen(closeWhenSeen))
249
        || (closeWhenDescribed != nil && closeWhenDescribed.described)
250
        || (closeWhenAchieved != nil && closeWhenAchieved.scoreCount != 0)
251
        || (closeWhenKnown != nil && gPlayerChar.knowsAbout(closeWhenKnown))
252
        || (closeWhenRevealed != nil && gRevealed(closeWhenRevealed))
253
        || closeWhenTrue)
254
255
    /*
256
     *   Has this goal been fully displayed?  The hint system automatically
257
     *   sets this to true when the last item in our hint list is
258
     *   displayed.
259
     *   
260
     *   You can use this, for example, to automatically remove the hint
261
     *   from the hint menu after it's been fully displayed.  (You might
262
     *   want to do this with a hint for a red herring, for example.  After
263
     *   the player has learned that the red herring is a red herring, they
264
     *   probably won't need to see that particular line of hints again, so
265
     *   you can remove the clutter in the menu by closing the hint after
266
     *   it's been fully displayed.)  To do this, simply add this to the
267
     *   Goal object:
268
     *   
269
     *.    closeWhenTrue = (goalFullyDisplayed)
270
     */
271
    goalFullyDisplayed = nil
272
273
    /*
274
     *   Check our menu state and update it if necessary.  Each time our
275
     *   parent menu is about to display, it'll call this on its sub-items
276
     *   to let them update their current states.  This method can promote
277
     *   the state to Open or Closed if the necessary conditions for the
278
     *   goal have been met.
279
     *   
280
     *   Sometimes it's more convenient to set a goal's state explicitly
281
     *   from a scripted event; for example, if the goal is associated
282
     *   with a scored achievement, awarding the goal's achievement will
283
     *   set the goal's state to Closed.  In these cases, there's no need
284
     *   to use this method, since you're managing the goal's state
285
     *   explicitly.  The purpose of this method is to make it easy to
286
     *   catch goal state changes that can be reached by several different
287
     *   routes; in these cases, you can just write a single test for
288
     *   those conditions in this method rather than trying to catch every
289
     *   possible route to the new conditions and writing code in all of
290
     *   those.
291
     *   
292
     *   The default implementation looks at our openWhenSeen property.
293
     *   If this property is not nil, then we'll check the object
294
     *   referenced in this property; if our current state is
295
     *   Undiscovered, and the object referenced by openWhenSeen has been
296
     *   seen by the player character, then we'll change our state to
297
     *   Open.  We'll make the corresponding check for openWhenDescribed.  
298
     */
299
    updateContents()
300
    {
301
        /* 
302
         *   If we're currently Undiscovered, and our openWhenSeen object
303
         *   has been seen by the player charater, change our state to
304
         *   Open.  Likewise, if our gating achievement has been scored,
305
         *   open the goal.  
306
         */
307
        if (goalState == UndiscoveredGoal && openWhen)
308
        {
309
            /* 
310
             *   the player has encountered our gating object, so open
311
             *   this goal 
312
             */
313
            goalState = OpenGoal;
314
        }
315
316
        /* 
317
         *   if we're currently Undiscovered or Open, and our Achievement
318
         *   has been scored, then change our state to Closed - once the
319
         *   goal has been achieved, there's no need to offer hints on the
320
         *   topic any longer 
321
         */
322
        if (goalState is in (UndiscoveredGoal, OpenGoal) && closeWhen)
323
        {
324
            /* the goal has been achieved, so close it */
325
            goalState = ClosedGoal;
326
        }
327
    }
328
329
    /* display a sub-item, keeping track of when we've shown them all */
330
    displaySubItem(idx, lastBeforeInput, eol)
331
    {
332
        /* do the inherited work */
333
        inherited(idx, lastBeforeInput, eol);
334
335
        /* if we just displayed the last item, note it */
336
        if (idx == menuContents.length())
337
            goalFullyDisplayed = true;
338
    }
339
340
    /* we're active in our parent menu if our goal state is Open */
341
    isActiveInMenu = (goalState == OpenGoal)
342
343
    /* 
344
     *   This goal's current state.  We'll start off undiscovered.  When a
345
     *   goal should be open from the very start of the game, this should
346
     *   be overridden and set to OpenGoal. 
347
     */
348
    goalState = UndiscoveredGoal
349
;
350
351
/*
352
 *   A Hint encapsulates one hint from a topic.  In many cases, hints can
353
 *   be listed in a topic simply as strings, rather than using Hint
354
 *   objects.  Hint objects provide a little more control, though; in
355
 *   particular, a Hint object can specify some additional code to run
356
 *   when the hint is shown, so that it can apply any side effects of
357
 *   showing the hint (for example, when a hint is shown, it could mark
358
 *   another Goal object as Open, which might be desirable if the hint
359
 *   refers to another topic that the player might not yet have
360
 *   encountered).  
361
 */
362
class Hint: MenuTopicSubItem
363
    /* the hint text */
364
    hintText = ''
365
366
    /*
367
     *   A list of other Goal objects that this hint references.  By
368
     *   default, when we show this hint for the first time, we'll promote
369
     *   each goal in this list from Undiscovered to Open.
370
     *   
371
     *   Sometimes, it's necessary to solve one puzzle before another can
372
     *   be solved.  In these cases, some hints for the first puzzle
373
     *   (which depends on the second), especially the later, more
374
     *   specific hints, might need to refer to the other puzzle.  This
375
     *   would make the player aware of the other puzzle even if they
376
     *   weren't already.  In such cases, it's a good idea to make sure
377
     *   that we make hints for the other puzzle available immediately,
378
     *   since otherwise the player might be confused by the absence of
379
     *   hints about it.  
380
     */
381
    referencedGoals = []
382
383
    /*
384
     *   Get my hint text.  By default, we mark as Open any goals listed
385
     *   in our referencedGoals list, then return our hintText string.
386
     *   Individual Hint objects can override this as desired to apply any
387
     *   additional side effects.
388
     */
389
    getItemText()
390
    {
391
        /* scan the referenced goals list */
392
        foreach (local cur in referencedGoals)
393
        {
394
            /* if this goal is not yet discovered, open it */
395
            if (cur.goalState == UndiscoveredGoal)
396
                cur.goalState = OpenGoal;
397
        }
398
399
        /* return our hint text */
400
        return hintText;
401
    }
402
;
403
404
/*
405
 *   A hint menu.  This same class can be used for the top-level hints
406
 *   menu and for sub-menus within the hints menu.
407
 *   
408
 *   The typical hint menu system will be structured into a top-level hint
409
 *   menu that contains a set of sub-menus for the main areas of the game;
410
 *   each sub-menu will have a series of Goal items, each Goal providing a
411
 *   set of answers to a particular question.  Something like this:
412
 *   
413
 *   topHintMenu: TopHintMenu 'Hints';
414
 *.  + HintMenu 'General Questions';
415
 *.  ++ Goal 'What am I supposed to be doing?' [answer, answer, answer];
416
 *.  ++ Goal 'Amusing things to try' [thing, thing, thing];
417
 *.  + HintMenu 'First Area';
418
 *.  ++ Goal 'How do I get past the shark?' [answer, answer, answer];
419
 *.  ++ Goal 'How do I open the fish tank?' [answer, answer, answer];
420
 *.  + HintMenu 'Second Area';
421
 *.  ++ Goal 'Where is the gold key?' [answer, answer, answer];
422
 *.  ++ Goal 'How do I unlock the gold door?' [answer, answer, answer];
423
 *   
424
 *   Note that there's no requirement that the hint menu tree takes
425
 *   exactly this shape.  A very small game could dispense with the
426
 *   submenus and simply put all of the goals directly in the top hint
427
 *   menu.  A very large game with lots of goals could add more levels of
428
 *   sub-menus to make it easier to navigate the large number of topics.  
429
 */
430
class HintMenu: MenuItem, HintMenuObject
431
    /* the menu's title */
432
    title = ''
433
434
    /* update our contents */
435
    updateContents()
436
    {
437
        local vec = new Vector(16);
438
        
439
        /* 
440
         *   First, run through all of our sub-items, and update their
441
         *   contents.  We only want to show our active contents, so we
442
         *   need to check with each item to find out which is active. 
443
         */
444
        foreach (local cur in allContents)
445
            cur.updateContents();
446
447
        /* create a vector containing all of our active items */
448
        foreach (local cur in allContents)
449
        {
450
            /* if this item is active, add it to the active vector */
451
            if (cur.isActiveInMenu)
452
                vec.append(cur);
453
        }
454
455
        /* set our contents list to the list of active items */
456
        contents = vec;
457
    }
458
459
    /* we're active in a menu if we have any active contents */
460
    isActiveInMenu = (contents.length() != 0)
461
462
    /* add a sub-item to our contents */
463
    addToContents(obj)
464
    {
465
        /* 
466
         *   add the sub-item to our allContents list rather than our
467
         *   active contents 
468
         */
469
        allContents += obj;
470
    }
471
472
    /* initialize our contents list */
473
    initializeContents()
474
    {
475
        /* sort our allContents list in the object-defined sorting order */
476
        allContents = allContents.sort(
477
            SortAsc, {a, b: a.compareForTopicSort(b)});
478
    }
479
480
    /* 
481
     *   our list of all of our sub-items (some of which may not be
482
     *   active, in which case they'll appear in this list but not in our
483
     *   'contents' list, which contains only active contents) 
484
     */
485
    allContents = []
486
;
487
488
/*
489
 *   A hint menu version of the long topic menu.
490
 */
491
class HintLongTopicItem: MenuLongTopicItem, HintMenuObject
492
    /* 
493
     *   presume these are always active - they're usually used for things
494
     *   like hint system instructions that should always be available 
495
     */
496
    isActiveInMenu = true
497
;
498
499
/*
500
 *   Top-level hint menu.  As a convenience, an object defined of this
501
 *   class will automatically register itself as the top-level hint menu
502
 *   during pre-initialization.  
503
 */
504
class TopHintMenu: HintMenu, PreinitObject
505
    /* register as the top-level hint menu during pre-initialization */
506
    execute() { hintManager.topHintMenuObj = self; }
507
;
508
509
/* ------------------------------------------------------------------------ */
510
/*
511
 *   The default hint system user interface implementation.  All of the
512
 *   hint-related verbs operate by calling methods in the object stored in
513
 *   the global variable gHintSystem, which we'll by default initialize
514
 *   with a reference to this object.  Games can replace this with their
515
 *   own implementations if desired.  
516
 */
517
hintManager: PreinitObject
518
    /* during pre-initialization, register as the global hint manager */
519
    execute() { gHintManager = self; }
520
    
521
    /*
522
     *   Disable hints - this is invoked by the HINTS OFF action.
523
     *   
524
     *   Some users don't like on-line hint systems because they find them
525
     *   to be too much of a temptation.  To address this concern, we
526
     *   provide this HINTS OFF command.  Players who want to ensure that
527
     *   their will-power won't crumble later on in the face of a
528
     *   difficult puzzle can type HINTS OFF early on, before the going
529
     *   gets rough; this will disable hints for the rest of the session.
530
     *   It's kind of like giving your credit card to a friend before
531
     *   going to the mall, making the friend promise that they won't let
532
     *   you spend more than such and such an amount, no matter how much
533
     *   you beg and plead.  
534
     */
535
    disableHints()
536
    {
537
        /* 
538
         *   Remember that hints have been disabled.  Keep this
539
         *   information in the transient session object, since we want
540
         *   the disabled status to last for the rest of this session,
541
         *   even if we restore or restart later.  
542
         */
543
        sessionHintStatus.hintsDisabled = true;
544
545
        /* acknowledge it */
546
        mainReport(gLibMessages.hintsDisabled);
547
    }
548
549
    /*
550
     *   The top-level hint menu.  This must be provided by the game, and
551
     *   should be set during initialization.  If this is nil, hints won't
552
     *   be available.
553
     *   
554
     *   We don't provide a default top-level hint menu because we want to
555
     *   give the game maximum flexibility in defining this object exactly
556
     *   as it wants.  For convenience, an object of class TopHintMenu
557
     *   will automatically register itself during pre-initialization -
558
     *   but note that there should be only one such object in the entire
559
     *   game, since if there are more than one, only one will be
560
     *   arbitrarily chosen as the registered object.  
561
     */
562
    topHintMenuObj = nil
563
564
    /*
565
     *   Show hints - invoke the hint system. 
566
     */
567
    showHints()
568
    {
569
        /* if there is no top-level hint menu, no hints are available */
570
        if (topHintMenuObj == nil)
571
        {
572
            mainReport(gLibMessages.hintsNotPresent);
573
            return;
574
        }
575
576
        /* if hints are disabled, reject the request */
577
        if (sessionHintStatus.hintsDisabled)
578
        {
579
            mainReport(gLibMessages.sorryHintsDisabled);
580
            return;
581
        }
582
583
        /* bring the hint menu tree up to date */
584
        topHintMenuObj.updateContents();
585
586
        /* if there are no hints available, say so and give up */
587
        if (topHintMenuObj.contents.length() == 0)
588
        {
589
            mainReport(gLibMessages.currentlyNoHints);
590
            return;
591
        }
592
        
593
        /* if we haven't warned about hints, do so now */
594
        if (!showHintWarning())
595
            return;
596
597
        /* display the hint menu */
598
        topHintMenuObj.display();
599
600
        /* all done */
601
        mainReport(gLibMessages.hintsDone);
602
    }
603
604
    /*
605
     *   Show a warning before showing any hints.  By default, we'll show
606
     *   this at most once per session or once per saved game.  Returns
607
     *   true if we are to proceed to the hints, nil if not.  
608
     */
609
    showHintWarning()
610
    {
611
        /* 
612
         *   If we have previously warned in this session, or if we've
613
         *   warned in a previous session and the same game was later
614
         *   saved and restored, don't warn again.  The transient session
615
         *   object tells us if we've asked in this session; the normal
616
         *   persistent object tells us if we've asked in a previous
617
         *   session that we've since saved and restored. 
618
         */
619
        if (!sessionHintStatus.hintWarning && !gameHintStatus.hintWarning)
620
        {
621
            /* 
622
             *   we haven't asked yet in either the session or the game,
623
             *   so show the warning now 
624
             */
625
            gLibMessages.showHintWarning();
626
627
            /* note that we've shown the warning */
628
            sessionHintStatus.hintWarning = true;
629
            gameHintStatus.hintWarning = true;
630
631
            /* don't proceed to hints now; let them ask again */
632
            return nil;
633
        }
634
635
        /* 
636
         *   They've already seen the warning before.  It's possible that
637
         *   they've seen it in a past session with the game and not
638
         *   otherwise during this session, but now that we're accessing
639
         *   the hint system once, don't bother with another warning for
640
         *   the rest of this session.  
641
         */
642
        sessionHintStatus.hintWarning = true;
643
644
        /* proceed to the hints */
645
        return true;
646
    }
647
;
648
649
/*
650
 *   We keep several pieces of information about the status of the hint
651
 *   system.  Some of it pertains to the current session, independently of
652
 *   any saving/restoring/restarting, so we keep this information in a
653
 *   transient object.  Some pertains to the present game, so we keep it
654
 *   in an ordinary persistent object, so that it's saved and restored
655
 *   along with the game.  
656
 */
657
transient sessionHintStatus: object
658
    /* flag: we've warned about the hint system in this session */
659
    hintWarning = nil
660
661
    /* flag: we've disabled hints for this session */
662
    hintsDisabled = nil
663
;
664
665
gameHintStatus: object
666
    /* flag: we've warned about the hint system in this session */
667
    hintWarning = nil
668
;
669