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

4b825dc642cb6eb9a060e54bf8d69288fbee4904cfad47cfa334b206c65f22086bcc5d63e6f70944
1
#charset "us-ascii"
2
3
/* 
4
 *   Copyright (c) 2000, 2006 Michael J. Roberts.  All Rights Reserved. 
5
 *   
6
 *   TADS 3 Library: verification
7
 *   
8
 *   This module defines classes related to "verification," which is the
9
 *   phase of command execution where the parser attempts to determine how
10
 *   logical a command is.  
11
 */
12
13
#include "adv3.h"
14
15
16
/* ------------------------------------------------------------------------ */
17
/*
18
 *   Verification result class.  Verification routines return a
19
 *   verification result describing whether or not an action is allowed,
20
 *   and how much sense the command seems to make.  When a verification
21
 *   fails, it must include a message describing why the command isn't
22
 *   allowed.
23
 *   
24
 *   It is important to understand that the purpose of verification
25
 *   results is to guess what's in the player's mind, not to reflect the
26
 *   full internal state of the game.  We use verification results to
27
 *   figure out what a player means with a command, so if we were to rely
28
 *   on information the player doesn't have, we would not correctly guess
29
 *   the player's intentions.  So, in choosing a verification result, only
30
 *   information that ought to be obvious to the player should be
31
 *   consdidered.
32
 *   
33
 *   For example, suppose we have a closed door; suppose further that the
34
 *   door happens to be locked, but that there's no way for the player to
35
 *   see that just by looking at the door.  Now, if the player types
36
 *   "close door," we should return "currently illogical" - common sense
37
 *   tells the player that the door is something that can be opened and
38
 *   closed, so we wouldn't return "always illogical," but the player can
39
 *   plainly see that the door is already closed and thus would know that
40
 *   it makes no sense to close it again.  In other words, the player
41
 *   would conclude looking at the door that closing it is currently
42
 *   illogical, so that's the result we should generate.
43
 *   
44
 *   What if the player types "open door," though?  In this case, should
45
 *   we return "currently illogical" as well, because the door is locked?
46
 *   The answer is no.  We know that the command won't succeed because we
47
 *   know from looking at the internal game state that the door is locked,
48
 *   but that doesn't matter - it's what the *player* knows that's
49
 *   important, not what the internal game state tells us.  So, what
50
 *   should we return here?  It might seem strange, but the correct result
51
 *   is "logical" - as far as the player is concerned, the door is
52
 *   something that can be opened and closed, and it is currently closed,
53
 *   so it makes perfect sense to open it.  
54
 */
55
class VerifyResult: MessageResult
56
    /* 
57
     *   Is the action allowed?  This returns true if the command can be
58
     *   allowed to proceed on the basis of the verification, nil if not.  
59
     */
60
    allowAction = true
61
62
    /*
63
     *   Is the action allowed as an implicit action?  This returns true
64
     *   if the command can be allowed to proceed AND the command can be
65
     *   undertaken simply because it's implied by another command, even
66
     *   though the player never explicitly entered the command.  We
67
     *   distinguish this from allowAction so that we can prevent certain
68
     *   actions from being undertaken implicitly; we might want to
69
     *   disallow an implicit action when our best guess is that a player
70
     *   should know better than to perform an action because it's
71
     *   obviously dangerous.  
72
     */
73
    allowImplicit
74
    {
75
        /* 
76
         *   by default, any allowable action is also allowed as an
77
         *   implicit action 
78
         */
79
        return allowAction;
80
    }
81
82
    /*
83
     *   Am I worse than another result?  Returns true if this result is
84
     *   more disapproving than the other. 
85
     */
86
    isWorseThan(other)
87
    {
88
        /* I'm worse if my result ranking is lower */
89
        return (resultRank < other.resultRank);
90
    }
91
92
    /* 
93
     *   compare to another: negative if I'm worse than the other, zero if
94
     *   we're the same, positive if I'm better 
95
     */
96
    compareTo(other)
97
    {
98
        /* compare based on result rankings */
99
        return resultRank - other.resultRank;
100
    }
101
102
    /* 
103
     *   Determine if I should appear in a result list before the given
104
     *   result object.  By default, this is true if I'm worse than the
105
     *   given result, but some types of results use special sorting
106
     *   orders.  
107
     */
108
    shouldInsertBefore(other)
109
    {
110
        /* 
111
         *   by default, I come before the other in a result list if I'm
112
         *   worse than the other, because we keep result lists in order
113
         *   from worst to best 
114
         */
115
        return compareTo(other) < 0;
116
    }
117
118
    /* 
119
     *   Determine if I'm identical to another result.  Note that it's
120
     *   possible for two items to compare the same but not be identical -
121
     *   compareTo() is concerned only with logicalness ranking, but
122
     *   identicalTo() determines if the two items are exactly the same.
123
     *   Some subclasses (such as LogicalVerifyResult) distinguish among
124
     *   items that compare the same but have different reasons for their
125
     *   rankings.  
126
     */
127
    identicalTo(other)
128
    {
129
        /* by default, I'm identical if my comparison shows I rank the same */
130
        return compareTo(other) == 0;
131
    }
132
    
133
    /*
134
     *   Our result ranking relative to other results.  Each result class
135
     *   defines a ranking level so that we can determine whether one
136
     *   result is better (more approving) or worse (more disapproving)
137
     *   than another.
138
     *   
139
     *   To allow easy insertion of new library extension result types or
140
     *   game-specific result types, we assign widely spaced rankings to
141
     *   the pre-defined results.  This is arbitrary; the only thing that
142
     *   matters in comparing two results is the order of the rank values.
143
     */
144
    resultRank = nil
145
146
    /* 
147
     *   Should we exclude plurals from being matched, when this type of
148
     *   result is present?  By default, we don't; some illogical types
149
     *   might want to exclude plurals because the result types indicate
150
     *   such obvious illogicalities.  
151
     */
152
    excludePluralMatches = nil
153
;
154
155
/*
156
 *   Verification result - command is logical and allowed.
157
 *   
158
 *   This can provide additional information ranking the likelihood of the
159
 *   command intepretation, which can be useful to distinguish among
160
 *   logical but not equally likely possibilities.  For example, if the
161
 *   command is "take book," and the actor has a book inside his or her
162
 *   backpack, and there is also a book on a table in the actor's
163
 *   location, it would make sense to take either book, but the game might
164
 *   prefer to take the book on the table because it's not already being
165
 *   carried.  The likelihood level can be used to rank these
166
 *   alternatives: if the object is being carried indirectly, a lower
167
 *   likelihood ranking would be returned than if the object were not
168
 *   already somewhere in the actor's inventory.  
169
 */
170
class LogicalVerifyResult: VerifyResult
171
    construct(likelihoodRank, key, ord)
172
    {
173
        /* remember my likelihood ranking */
174
        likelihood = likelihoodRank;
175
176
        /* remember my key value */
177
        keyVal = key;
178
179
        /* remember my list order */
180
        listOrder = ord;
181
    }
182
183
    /* am I worse than the other result? */
184
    isWorseThan(other)
185
    {
186
        /* 
187
         *   I'm worse if my result ranking is lower; or, if we are both
188
         *   LogicalVerifyResult objects, I'm worse if my likelihood is
189
         *   lower. 
190
         */
191
        if (resultRank == other.resultRank)
192
            return likelihood < other.likelihood;
193
        else
194
            return inherited(other);
195
    }
196
197
    /* compare to another result */
198
    compareTo(other)
199
    {
200
        /* 
201
         *   if we're not both of the same rank (i.e., 'logical'), inherit
202
         *   the default comparison 
203
         */
204
        if (resultRank != other.resultRank)
205
            return inherited(other);
206
207
        /* 
208
         *   we're both 'logical' results, so compare based on our
209
         *   respective likelihoods 
210
         */
211
        return likelihood - other.likelihood;
212
    }
213
214
    /* determine if I go in a result list before the given result */
215
    shouldInsertBefore(other)
216
    {
217
        /* if we're not both of the same rank, use the default handling */
218
        if (resultRank != other.resultRank)
219
            return inherited(other);
220
221
        /* 
222
         *   we're both 'logical' results, so order in the list based on
223
         *   our priority ordering; if we're of the same priority, use the
224
         *   default ordering 
225
         */
226
        if (listOrder != other.listOrder)
227
            return listOrder < other.listOrder;
228
        else
229
            return inherited(other);
230
    }
231
232
    /* determine if I'm identical to another result */
233
    identicalTo(other)
234
    {
235
        /*
236
         *   I'm identical if I compare the same and my key value is the
237
         *   same.
238
         */
239
        return compareTo(other) == 0 && keyVal == other.keyVal;
240
    }
241
242
    /*
243
     *   The likelihood of the command - the higher the number, the more
244
     *   likely.  We use 100 as the default, so that there's plenty of
245
     *   room for specific rankings above or below the default. Particular
246
     *   actions might want to rank likelihoods based on action-specific
247
     *   factors.  
248
     */
249
    likelihood = 100
250
251
    /*
252
     *   Our list ordering.  This establishes how we are entered into the
253
     *   master results list relative to other 'logical' results.  Results
254
     *   are entered into the master list in ascending list order, so a
255
     *   lower order number means an earlier place in the list.
256
     *   
257
     *   The list ordering is more important than the likelihood ranking.
258
     *   Suppose we have two items: one is at list order 10 and has
259
     *   likelihood 100, and the other is at list order 20 and has
260
     *   likelihood 50.  The order of the likelihoods stored in the list
261
     *   will be (100, 50).  This is inverted from the normal ordering,
262
     *   which would put the worst item first.
263
     *   
264
     *   The point of this ordering is to allow for logical results with
265
     *   higher or lower importances in establishing the likelihood.  The
266
     *   library uses the following list order values:
267
     *   
268
     *   100 - the default ranking.  This is used in most cases.
269
     *   
270
     *   150 - secondary ranking.  This is used for rankings that aren't
271
     *   of great importance but which can be useful to distinguish
272
     *   objects in cases where no more important rankings are present.
273
     *   The library uses this for precondition verification rankings.  
274
     */
275
    listOrder = 100
276
277
    /* 
278
     *   my key value, to distinguish among different results with the
279
     *   same likelihood ranking 
280
     */
281
    keyVal = ''
282
283
    /* result rank - we're the most approving kind of result */
284
    resultRank = 100
285
;
286
287
/*
288
 *   Verification result - command is logical and allowed, but is
289
 *   dangerous.  As with all verify results, this should reflect our best
290
 *   guess as to the player's intentions, so this should only be used when
291
 *   it is meant to be obvious to the player that the action is dangerous.
292
 */
293
class DangerousVerifyResult: VerifyResult
294
    /*
295
     *   don't allow dangerous actions to be undertaken implicitly - we do
296
     *   allow these actions, but only when explicitly requested 
297
     */
298
    allowImplicit = nil
299
300
    /* result rank - we're only slightly less approving than 'logical' */
301
    resultRank = 90
302
303
    /* this result indicates danger */
304
    isDangerous = true
305
;
306
307
/*
308
 *   Verification result - command is currently illogical due to the state
309
 *   of the object, but might be logically applied to the object at other
310
 *   times.  For example, "open door" on a door that's already open is
311
 *   illogical at the moment, but makes more sense than opening something
312
 *   that has no evident way to be opened or closed to begin with.
313
 */
314
class IllogicalNowVerifyResult: VerifyResult
315
    /* the command isn't allowed */
316
    allowAction = nil
317
318
    /* result rank */
319
    resultRank = 40
320
;
321
322
/*
323
 *   Verification result - command is currently illogical, because the
324
 *   state that the command seeks to impose already obtains.  For example,
325
 *   we're trying to open a door that's already open, or drop an object
326
 *   that we're not carrying.
327
 *   
328
 *   This is almost exactly the same as an "illogical now" result, so this
329
 *   is a simple subclass of that result type.  We act almost the same as
330
 *   an "illogical now" result; the only reason to distinguish this type is
331
 *   that it's an especially obvious kind of condition, so we might want to
332
 *   use it to exclude some vocabulary matches that we wouldn't normally
333
 *   exclude for the more general "illogical now" result type.  
334
 */
335
class IllogicalAlreadyVerifyResult: IllogicalNowVerifyResult
336
    /* exclude plural matches when this result type is present */
337
    excludePluralMatches = true
338
;
339
340
/*
341
 *   Verification result - command is always illogical, regardless of the
342
 *   state of the object.  "Close fish" might fall into this category.  
343
 */
344
class IllogicalVerifyResult: VerifyResult
345
    /* the command isn't allowed */
346
    allowAction = nil
347
348
    /* result rank - this is the most disapproving of the disapprovals */
349
    resultRank = 30
350
;
351
352
/*
353
 *   Verification result - command is always illogical, because it's trying
354
 *   to use an object on itself in some invalid way, as in PUT BOX IN BOX.
355
 *   
356
 *   This is almost identical to a regular always-illogical result, so
357
 *   we're a simple subclass of that result type.  We distinguish these
358
 *   from the basic always-illogical type because it's especially obvious
359
 *   that the "self" kind is illogical, so we might in some cases want to
360
 *   exclude a vocabulary match for the "self" kind that we wouldn't
361
 *   exclude for the basic kind.  
362
 */
363
class IllogicalSelfVerifyResult: IllogicalVerifyResult
364
    /* exclude plural matches when this result type is present */
365
    excludePluralMatches = true
366
;
367
368
/*
369
 *   Verification result - command is logical and allowed, but is
370
 *   non-obvious on this object.  This should be used when the command is
371
 *   logical, but should not be obvious to the player.  When this
372
 *   verification result is present, the command is allowed when performed
373
 *   explicitly but will never be taken as a default.
374
 *   
375
 *   In cases of ambiguity, a non-obvious object is equivalent to an
376
 *   always-illogical object.  A non-obvious object *appears* to be
377
 *   illogical at first glance, so we want to treat it the same as an
378
 *   ordinarily illogical object if we're trying to choose among ambiguous
379
 *   objects.  
380
 */
381
class NonObviousVerifyResult: VerifyResult
382
    /* 
383
     *   don't allow non-obvious actions to be undertaken implicitly - we
384
     *   allow these actions, but only when explicitly requested 
385
     */
386
    allowImplicit = nil
387
388
    /* 
389
     *   non-obvious objects are illogical at first glance, so rank them
390
     *   the same as objects that are actually illogical 
391
     */
392
    resultRank = (IllogicalVerifyResult.resultRank)
393
;
394
395
/*
396
 *   Verification result - object is inaccessible.  This should be used
397
 *   when a command is applied to an object that is not accessibile in a
398
 *   sense required for the command; for example, "look at" requires that
399
 *   its target object be visible, so a "look at" command in the dark
400
 *   would fail with this type of result. 
401
 */
402
class InaccessibleVerifyResult: VerifyResult
403
    /* the command isn't allowed */
404
    allowAction = nil
405
406
    /*
407
     *   This ranks below any illogical result - inaccessibility is a
408
     *   stronger disapproval than mere illogicality.  
409
     */
410
    resultRank = 10
411
;
412
413
/*
414
 *   Default 'logical' verify result.  If a verification result list
415
 *   doesn't have an explicitly set result, this is the default value. 
416
 */
417
defaultLogicalVerifyResult: LogicalVerifyResult
418
    showMessage()
419
    {
420
        /* the default logical result has no message */
421
    }
422
    keyVal = 'default'
423
;
424
425
/*
426
 *   Verification result list.  
427
 */
428
class VerifyResultList: object
429
    construct()
430
    {
431
        /* initialize the results vector */
432
        results_ = new Vector(5);
433
    }
434
435
    /*
436
     *   Add a result to our result list.  
437
     */
438
    addResult(result)
439
    {
440
        local i;
441
        
442
        /*
443
         *   Find the insertion point.  We want to keep the results sorted
444
         *   in order from worst to best, so insert this result before the
445
         *   first item in our list that's better than this item. 
446
         */
447
        for (i = 1 ; i <= results_.length() ; ++i)
448
        {
449
            /* 
450
             *   if it's exactly the same as this item, don't add it -
451
             *   keep only one of each unique result 
452
             */
453
            if (result.identicalTo(results_[i]))
454
                return;
455
            
456
            /* 
457
             *   If the new result is to be inserted before the result at
458
             *   the current index, insert the new result at the current
459
             *   index.  
460
             */
461
            if (result.shouldInsertBefore(results_[i]))
462
                break;
463
        }
464
        
465
        /* add the result to our list at the index we found */
466
        results_.insertAt(i, result);
467
    }
468
469
    /* 
470
     *   Is the action allowed?  We return true if we have no results;
471
     *   otherwise, we allow the action if *all* of our results allow it,
472
     *   nil if even one disapproves.  
473
     */
474
    allowAction()
475
    {
476
        /* approve if the effective result approves */
477
        return results_.indexWhich({x: !x.allowAction}) == nil;
478
    }
479
480
    /*
481
     *   Do we exclude plural matches?  We do if we have at least one
482
     *   result that excludes plural matches. 
483
     */
484
    excludePluralMatches()
485
    {
486
        /* exclude plural matches if we have any result that says to */
487
        return results_.indexWhich({x: x.excludePluralMatches}) != nil;
488
    }
489
490
    /*
491
     *   Is the action allowed as an implicit action?  Returns true if we
492
     *   have no results; otherwise, returns true if *all* of our results
493
     *   allow the implicit action, nil if even one disapproves.  
494
     */
495
    allowImplicit()
496
    {
497
        /* search for disapprovals; if we find none, allow it */
498
        return results_.indexWhich({x: !x.allowImplicit}) == nil;
499
    }
500
501
    /*
502
     *   Show the message.  If I have any results, we'll show the message
503
     *   for the effective (i.e., most disapproving) result; otherwise we
504
     *   show nothing.  
505
     */
506
    showMessage()
507
    {
508
        local res;
509
        
510
        /* 
511
         *   Find the first result that disapproves.  Only disapprovers
512
         *   will have messages, so we need to find a disapprover.
513
         *   Entries are in ascending order of approval, and we want the
514
         *   most disapproving disapprover, so take the first one we find.
515
         */
516
        if ((res = results_.valWhich({x: !x.allowAction})) != nil)
517
            res.showMessage();
518
    }
519
520
    /*
521
     *   Get my effective result object.  If I have no explicitly-set
522
     *   result object, my effective result is the defaut logical result.
523
     *   Otherwise, we return the most disapproving result in our list.  
524
     */
525
    getEffectiveResult()
526
    {
527
        /* if our list is empty, return the default logical result */
528
        if (results_.length() == 0)
529
            return defaultLogicalVerifyResult;
530
531
        /* 
532
         *   return the first item in the list - we keep the list sorted
533
         *   from worst to best, so the first item is the most
534
         *   disapproving result we have 
535
         */
536
        return results_[1];
537
    }
538
539
    /*
540
     *   Compare my cumulative result (i.e., my most disapproving result)
541
     *   to that of another result list's cumulative result.  Returns a
542
     *   value suitable for sorting: -1 if I'm worse than the other one, 0
543
     *   if we're the same, and 1 if I'm better than the other one.  This
544
     *   can be used to compare the cumulative verification results for
545
     *   two objects to determine which object is more logical.  
546
     */
547
    compareTo(other)
548
    {
549
        local lst1;
550
        local lst2;
551
        local idx;
552
553
        /* get private copies of the two lists */
554
        lst1 = results_.toList();
555
        lst2 = other.results_.toList();
556
557
        /* keep going until we find differing items or run out of items */
558
        for (idx = 1 ; idx <= lst1.length() || idx <= lst2.length(); ++idx)
559
        {
560
            local a, b;
561
            local diff;
562
563
            /*
564
             *   Get the current item from each list.  If we're past the
565
             *   end of one or the other list, use the default logical
566
             *   result as the current item from that list.  
567
             */
568
            a = idx <= lst1.length() ? lst1[idx] : defaultLogicalVerifyResult;
569
            b = idx <= lst2.length() ? lst2[idx] : defaultLogicalVerifyResult;
570
571
            /*
572
             *   If the two items have distinct rankings, simply return
573
             *   the sense of the ranking. 
574
             */
575
            if ((diff = a.compareTo(b)) != 0)
576
                return diff;
577
578
            /*
579
             *   The two items at the current position have equivalent
580
             *   rankings, so ignore them for the purposes of comparing
581
             *   these two items.  Simply proceed to the next item in each
582
             *   list.  Before we do, though, check to see if we can
583
             *   eliminate that current item from our own list - if we
584
             *   have an identical item (not just ranked the same, but
585
             *   actually identical) in the other list, throw the item out
586
             *   of both lists.  
587
             */
588
            for (local j = 1 ; j < lst2.length() ; ++j)
589
            {
590
                /* 
591
                 *   if this item in the other list is identical to the
592
                 *   current item from our list, throw out both items 
593
                 */
594
                if (lst2[j].identicalTo(a))
595
                {
596
                    /* remove the items from both lists */
597
                    lst1 -= a;
598
                    lst2 -= lst2[j];
599
600
                    /* consider the new current item at this position */
601
                    --idx;
602
603
                    /* no need to scan any further */
604
                    break;
605
                }
606
            }
607
        }
608
609
        /* 
610
         *   We've run out of items in both lists, so everything must have
611
         *   been identical in both lists.  Since we have no 'verify' basis
612
         *   for preferring one object over the other, fall back on our
613
         *   intrinsic vocabLikelihood values as a last resort.
614
         */
615
        return obj_.obj_.vocabLikelihood - other.obj_.obj_.vocabLikelihood;
616
    }
617
618
    /*
619
     *   Determine if we match another verify result list after remapping.
620
     *   This determines if the other verify result is equivalent to us
621
     *   after considering the effects of remapping.  We'll return true if
622
     *   all of the following are true:
623
     *   
624
     *   - compareTo returns zero, indicating that we have the same
625
     *   weighting in the verification results
626
     *   
627
     *   - we refer to the same object after remapping; the effective
628
     *   object after remapping is our original resolved object, if we're
629
     *   not remapped, or our remap target if we are
630
     *   
631
     *   - we use the object for the same action and in the same role
632
     *   
633
     *   Note: this can only be called on remapped results.  Results can
634
     *   only be combined in the first place when remapped, so there's no
635
     *   need to ever call this on an unremapped result.  
636
     */
637
    matchForCombineRemapped(other, action, role)
638
    {
639
        /* if our verification values aren't identical, we can't combine */
640
        if (compareTo(other) != 0)
641
            return nil;
642
643
        /* 
644
         *   check the other - how we check depends on whether it's been
645
         *   remapped or not 
646
         */
647
        if (other.remapTarget_ == nil)
648
        {
649
            /* 
650
             *   The other one hasn't been remapped, so our remapped
651
             *   object, action, and role must match the other's original
652
             *   object, action, and role.  Note that to compare the two
653
             *   actions, we compare their baseActionClass properties,
654
             *   since this property gives us the identifying canonical
655
             *   base class for the actions.  
656
             */
657
            return (remapTarget_ == other.obj_.obj_
658
                    && remapAction_.baseActionClass == action.baseActionClass
659
                    && remapRole_ == role);
660
        }
661
        else
662
        {
663
            /*
664
             *   The other one has been remapped as well, so our remapped
665
             *   object, action, and role must match the other's remapped
666
             *   object, action, and role. 
667
             */
668
            return (remapTarget_ == other.remapTarget_
669
                    && remapAction_.baseActionClass
670
                       == other.remapAction_.baseActionClass
671
                    && remapRole_ == other.remapRole_);
672
        }
673
    }
674
675
    /*
676
     *   The remapped target object.  This will filled in during
677
     *   verification if we decide that we want to remap the nominal
678
     *   object of the command to a different object.  This should be set
679
     *   to the ultimate target object after all remappings.  
680
     */
681
    remapTarget_ = nil
682
683
    /* the action and role of the remapped action */
684
    remapAction_ = nil
685
    remapRole_ = nil
686
687
    /* our list of results */
688
    results_ = []
689
690
    /* 
691
     *   The ResolveInfo for the object being verified.  Note that this
692
     *   isn't saved until AFTER the verification is completed.  
693
     */
694
    obj_ = nil
695
696
    /* 
697
     *   The original list index for this result.  We use this when sorting
698
     *   a list of results to preserve the original ordering of otherwise
699
     *   equivalent items.  
700
     */
701
    origOrder = 0
702
;
703