forked from erkyrath/plotex
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdocs.html
More file actions
639 lines (474 loc) · 21.8 KB
/
Copy pathdocs.html
File metadata and controls
639 lines (474 loc) · 21.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
<!doctype HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>PlotEx: Reference Documentation</title>
<style type="text/css">
pre { margin-left: 3em; }
dl { margin-left: 3em; }
dt { font-family: monospace; }
.Spell { font-variant: small-caps; }
</style>
</head>
<body>
<h2>PlotEx: Reference Documentation</h2>
<p>
<em>(Release 1.1.1. The PlotEx script is in the public domain.)</em>
</p>
<ul>
<li><a href="index.html">Learn why PlotEx exists</a>
<li><a href="plotex.py">Look at the PlotEx script</a> (or, <a href="plotex3.py">Python 3</a> version)
<li>Look at the <a href="enchanter.py">Enchanter example</a> or the <a href="blank.py">blank example</a>
<li><a href="plotex.tar.gz">Download the script with all sample scenarios</a>
</ul>
<p>
PlotEx allows you to build a web of puzzle and event constraints (what needs to happen before what, what requires what tools, and so on). Then it computes all the consequences of the scenario; it shows you what states can and cannot be reached.
</p>
<p>
(See my <a href="index.html">long and discursive explanation of this idea.</a>)
</p>
<h3>Command-line options</h3>
<p>
To run PlotEx with the included sample scenario, just type
</p>
<pre>
python plotex.py
</pre>
<p>
(If you are a Python 3 user, you'll have to use the alternate version, <code>plotex3.py</code>. It's easiest to just copy it over <code>plotex.py</code>, so that the example scripts like <code>enchanter.py</code> can import it properly.)
</p>
<p>
To run another scenario, just invoke it the same way:
</p>
<pre>
python enchanter.py
</pre>
<p>
Either way, invoking it without arguments gives the default output. PlotEx will start at the <code>Start</code> state, run as far as possible, and display all the final states (states from which no more actions are possible). For each final state it will display the actions that got there (and how many actions it took). An asterisk marks states which no other (displayed) state is better than.
</p>
<p>
Note that if an argument accepts multiple values, you can use either multiple arguments ("--start S1 --start S2") or comma-separated arguments ("--start S1,S2").
</p>
<dl>
<dt>-s, --start STATE(S)
<dd>Begin in this state, rather than <code>Start</code>. You may give multiple states; PlotEx will assume they are all available to begin with.
<dt>--startwith QUALITY(IES)
<dd>Add this (boolean) quality (or qualities) to the start state (each of the start states, if more than once). This is an easy way to start in a custom state, without having to define it in the scenario file.
<dt>--block ACTION(S)
<dd>Perform the run without permitting the named action (or actions).
<dt>--withhold ACTION(S)
<dd>Perform the run without permitting the named actions, as far as possible. Then continue the run with those actions back in. (This will produce the same states as --block, but the histories will only show the actions in the second phase. This may be useful, or it may not.)
<dt>-f, --filter QUALITY(IES)
<dd>Only show states that contain the named quantity (at better than False/0/None).
<dt>-H, --history ACTION(S)
<dd>Only show states in whose history the named action occurs. (Note that if there are several ways to reach a state, PlotEx only records one of them. So this argument may not work for optional actions.)
<dt>-m, --showmed
<dd>Show intermediate states as well as final states.
<dt>--showin
<dd>For each state, show the actions that led to it.
<dt>--showout
<dd>For each state, show the actions that led away from it.
<dt>-a, --showall
<dd>Show intermediate states, plus the in and out actions for every state.
<dt>-d, --diff
<dd>When listing several states, show only what's different about each one.
<dt>-c, --count
<dd>Show only the number of states found, rather than displaying each one.
<dt>--graphviz FILE
<dd>Generate a <a href="http://www.graphviz.org/">GraphViz</a> data file (DOT or *.gv format) containing all the run's states (including intermediate states). If you use the --filter or --history options, those states will be colored.
<dt>--graphml FILE
<dd>Generate a <a href="http://www.fim.uni-passau.de/fileadmin/files/lehrstuhl/brandenburg/projekte/gml/gml-technical-report.pdf">GML</a> data file containing all the run's states.
<dt>-t, --test TEST(S)
<dd>Perform each of the named tests. Show how many fail. (See Tests, below.) When running tests, most of the other arguments have no effect; only --genlimit is meaningful. (Start states, blocks, etc are defined by the individual tests.)
<dt>-T, --alltests
<dd>Perform all of the tests in the scenario file. Show how many fail.
<dt>--genlimit NUMBER
<dd>Normally, if the run generates more than 10000 states, PlotEx assumes you have an infinite loop and gives up. This argument allows you to raise (or lower) that limit. (You can also define a <code>genlimit</code> value in your scenario file.)
<dt>--noopt
<dd>Switch off certain optimizations in the PlotEx algorithm. These optimizations are normally safe, but may go wrong if certain bizarre actions are defined.
</dl>
<h3>Setting up a PlotEx scenario</h3>
<p>
The easiest way to do this is to copy the <a href="blank.py">blank scenario example file</a>, and start filling it in. A minimal scenario looks like this:
</p>
<pre>
from plotex import *
class Scenario(ScenarioClass):
Start = State()
# Actions go here
# This parses and carries out the command-line options, using the Scenario.
shell(Scenario)
</pre>
<p>
A scenario must be in the same directory as <code>plotex.py</code>. To run it, type
</p>
<pre>
python yourscenario.py
</pre>
<h3>States</h3>
<p>
The simplest state looks like <code>State()</code>. This is a state with no qualities at all. Your <code>Start</code> state will often look like this, although it doesn't have to.
</p>
<p>
A more complex state is defined with any number of keyword arguments. Each defines a quality. For example:
</p>
<pre>
State(food=True, coin=3)
State(name='fred', spells=['light', 'unlock'])
</pre>
<p>
This defines a boolean quality <code>food</code>, and a numeric quality <code>coin</code>.
</p>
<p>
Qualities can only have a few types:
</p>
<ul>
<li>boolean: True or False. (In Python, these constants must be capitalized.) True is better than False.
<li>number: An integer, 0 or up. (Negative numbers will confuse PlotEx, unfortunately.) Bigger numbers are better.
<li>string: Any quoted string. Strings have no better/worse scale, except that any other string beats ''.
<li>set: A list of strings, in square brackets. (This looks like a Python list, but PlotEx treats it as an unordered set.) A set is better than any subset of it.
</ul>
<p>
A quality must have the same type everywhere in your scenario. You can't define one state with <code>State(food=True)</code>, and another with <code>State(food='cheese')</code>.
</p>
<p>
An empty quality (False, 0, '', or []) is equivalent to leaving out the quality entirely. (Except that the type is checked. You could use this to type-define qualities, but there's really no need to.)
</p>
<p>
Quality names are normally lower-case. (This isn't required, but in my examples I stick to the rule that qualities are lower-case, and states and actions are capitalized. I recommend you do to.)
</p>
<p>
If a quality names begins with an underscore, it is considered to have an inverted scale. That is, <code>State(coin=2)</code> is better than <code>State(coin=1)</code>, which is better than <code>State()</code>. However, <code>State(_pain=1)</code> is better than <code>State(_pain=2)</code>; <code>State()</code> is better than either of them.
</p>
<p>
(But you still can't use negative integers. Sorry.)
</p>
<h3>Actions</h3>
<p>
An action turns a state into another state, by changing its qualities. Some actions check for qualities first. If the check fails, the action does nothing to that state.
</p>
<p>
Actions can be chained together. In fact, this is usually the way you construct conditional actions. You take a "check for condition" action and a "change some stuff" action, and chain them together. If one step in the chain fails, the whole chained action fails (and does nothing).
</p>
<p>
These are the standard actions. You can also define your own, but that's Python work, and if you're that sort of person you can pick it up from the source code.
</p>
<p><strong>
Set(qual=VALUE, qual=VALUE, ...)
</strong></p>
<p>
Set some qualities. This always succeeds (even if the state already had those qualities at a different level, or the same level). You can set any number of qualities this way.
</p>
<p>
Note that <code>Set(food=False)</code> will remove the (boolean) <code>food</code> quality entirely.
</p>
<p><strong>
Reset(qual=VALUE, qual=VALUE, ...)
</strong></p>
<p>
Clear the state out, and then set some qualities. This wipes the state completely; the output is the same no matter what the input state was.
</p>
<p><strong>
Has(qual=VALUE, qual=VALUE, ...)
</strong></p>
<p>
Check whether the state has all of the given qualities, <em>or better</em>. <code>Has(coin=1)</code> will check whether the state has at least one coin. On the other hand, <code>Has(_pain=2)</code> checks whether the state's pain is <em>at most</em> two. (Because <code>_pain</code> starts with an underscore, it has an inverted scale.)
</p>
<p>
If the check succeeds, the state is returned unchanged. (This is only useful because of the <code>Chain</code> action. See below.) If the state doesn't have all of the qualities, the action fails.
</p>
<p>
Note that checking <code>Has(flag=False)</code> is useless, because every state has a <code>flag</code> value of False or better. However, <code>Has(_flag=False)</code> does make sense, because <code>_flag</code> is inverted.
</p>
<p><strong>
HasAny(qual=VALUE, qual=VALUE, ...)
</strong></p>
<p>
Like <code>Has()</code>, but the state only needs to have <em>one</em> of the given qualities.
</p>
<p><strong>
HasDifferent('qual', 'STR', 'STR', ...)
</strong></p>
<p>
Checks whether the state's quality has a value different from all of the given strings. If it doesn't, or if the state lacks the quality entirely (or is ''), then the action fails.
</p>
<p>
The quality must be a string quality. Because of the way Python works, the quality name (and all the strings) must be quoted.
</p>
<p><strong>
Lose('qual', 'qual', ...)
</strong></p>
<p>
If the state does not have all of the given qualities, the action fails. Otherwise, it loses them all. (Boolean qualities become False, numeric ones become 0, strings become '', sets become the empty set.)
</p>
<p>
Here, again, the quality names must be quoted.
</p>
<p>
<code>Lose(flag)</code> is equivalent to <code>Chain(Has(flag=True), Set(flag=False))</code>. But it's shorter.
</p>
<p><strong>
Once()
</strong></p>
<p>
Checks whether this action has ever succeeded before. If it never has, it succeeds now. All subsequent times, it fails. (This works by setting an inverted boolean quality.)
</p>
<p>
You might say, for example, <code>Chain(Once(), Set(blessing=True))</code>. This action will set the <code>blessing</code> quality, but only once per game.
</p>
<p><strong>
Once('_qual')
</strong></p>
<p>
Same thing, but you specify the quality name. It must start with an underscore, and it must be quoted.
</p>
<p><strong>
Once(ACTION)
</strong></p>
<p>
Same thing, except you include the action to once-ify <em>inside</em> the <code>Once()</code> action. Usually this is simpler than chaining it. E.g., <code>Once(Set(blessing=True))</code>
</p>
<p><strong>
Increment('qual', LIMIT)
</strong></p>
<p>
If the given quality is LIMIT or more, this fails. Otherwise, it increases the quality by one. (It must be a numeric quality, of course.) The quality name must be quoted.
</p>
<p>
If you write <code>Increment('coin', 10)</code> with no chained conditions, PlotEx will promptly take the action ten times in a row. (Players naturally want <em>all the coins</em>.) If that's your intent, it's easier to do <code>Set(coin=10)</code>. Otherwise, think about your preconditions.
</p>
<p><strong>
Decrement('qual', LIMIT)
</strong></p>
<p>
Same thing, except the quality decreases by one. You can omit the LIMIT, in which case it defaults to zero.
</p>
<p><strong>
Include('qual', 'STR', 'STR', ...)
</strong></p>
<p>
The given quality must be a set. This adds the given strings to the set, if they're not there already. This action always succeeds.
</p>
<p>
The quality name (and all the strings) must be quoted.
</p>
<p><strong>
Exclude('qual', 'STR', 'STR', ...)
</strong></p>
<p>
If the set quality does not have all of the given strings, the action fails. If it does, it loses them.
</p>
<p><strong>
Count('qual', NUMBER)
</strong></p>
<p>
The given quality must be a set. This succeeds if the set has at least NUMBER strings in it.
</p>
<p><strong>
Chain(ACTION, ACTION, ACTION...)
</strong></p>
<p>
This is the one that ties them all together. It tries to perform each of the listed actions, in order, on the state. If any of them fail, the whole chain fails. If they all succeed, the final product is returned.
</p>
<p>
Some examples from Enchanter:
</p>
<p>
<code>Chain(Has(rezrov=True), Set(incourtyard=True))</code> -- If the player has the <span class="Spell">rezrov</span> spell, set <code>incourtyard</code>, indicating that the player has access to the courtyard.
</p>
<p>
<code>Chain(Lose('kulcad'), Set(melbor=True))</code> -- If the player has the <span class="Spell">kulcad</span> spell, remove it, and then gain the <span class="Spell">melbor</span> spell. (You can <span class="Spell">kulcad</span> the jewelled box to open it and access the contents. But <span class="Spell">kulcad</span> is a one-shot spell.) If the player doesn't have the <code>kulcad</code> quality, this action does nothing.
</p>
<p>
<code>Chain(Has(intower=True), Has(gondar=True), HasAny(vaxum=True, cleesh=True), Set(krill=True))</code> -- Check that the player is in the tower, that the player has <span class="Spell">gondar</span>, and that the player has <em>either</em> <span class="Spell">cleesh</span> or <span class="Spell">vaxum</span>. If all of those are true, set <code>krill</code>, indicating that the player has access to Krill.
</p>
<p><strong>
Choice(ACTION, ACTION, ACTION...)
</strong></p>
<p>
Try to perform each of the listed actions, in order. As soon as <em>one</em> of them succeeds, return the result. If they all fail, the whole action fails.
</p>
<h3>Tests</h3>
<p>
A test is essentially a complete invocation of PlotEx, wrapped up with some outcome conditions. When you invoke the test, it runs and verifies the outcome is what you expected.
</p>
<p>
You can include some tests in your scenario; they're effectively unit tests of your story. If you alter one of your actions, you can re-test to see if the outcomes all still work (or don't work) correctly.
</p>
<p><strong>
Test(can=ACTION)
</strong></p>
<p>
Run through the scenario, starting with the <code>Start</code> state. Then see if the given action succeeds on any of the reachable states. (It doesn't have to succeed on all of them, just at least one.)
</p>
<p>
For example, you might define <code>Test(can=Has(win=True))</code> to check whether some winning state is reachable.
</p>
<p>
The actions have no effect on the scenario states (even actions like <code>Lose</code>). They're just checked to see whether they succeed or fail.
</p>
<p><strong>
Test(cannot=ACTION)
</strong></p>
<p>
Run through the scenario, and check that the action succeeds on <em>no</em> reachable states.
</p>
<p><strong>
Test(gets='qual')
</strong></p>
<p>
Run through the scenario, and see if any reachable state has the given quality. (At better than False/0/etc.)
</p>
<p><strong>
Test(getsnot='qual')
</strong></p>
<p>
Run through the scenario, and check that <em>no</em> reachable state has the given quality. (All states must lack it; or, equivelantly, have it at False/0/etc.)
</p>
<p><strong>
Test(includes=ACTION)
</strong></p>
<p>
Run through the scenario, and check whether any reachable state includes the action in its history. (That is, whether that action was taken in the process of reaching that state.)
</p>
<p>
Note that if there are several ways to reach a state, PlotEx only records one of them. (It tries to minimize the number of actions.) So this test will be most useful for critical actions.
</p>
<p><strong>
Test(excludes=ACTION)
</strong></p>
<p>
Run through the scenario, and check that <em>no</em> reachable state has the action in its history.
</p>
<p><strong>
Test(start=STATE, ...)<br>
Test(start=[STATE, STATE, ...], ...)
</strong></p>
<p>
By including <code>start=STATE</code> in any test, you can control where the test begins its run. The default is <code>Start</code>. You can also have it start with a bunch of states.
</p>
<p>
(These may be named states, or ones created on the spot. <code>start=State(food=True)</code> would start the test in a state with only the <code>food</code> quality.)
</p>
<p><strong>
Test(block=ACTION, ...)<br>
Test(block=[ACTION, ACTION, ...], ...)
</strong></p>
<p>
By including <code>block=ACTION</code> in any test, you can mark the given action (or list of actions) as out-of-bounds for that test's run.
</p>
<p>
Note that you can include multiple positive or negative criteria in the test. For example:
</p>
<p>
<code>Test(gets='coin', includes=KillDragon)</code> -- Looks for a state that has the <code>coin</code> quality (greater than 0) <em>and</em> passed through the <code>KillDragon</code> action.
</p>
<p>
<code>Test(gets=['food', 'lamp'])</code> -- Looks for a state that has both of those qualities.
</p>
<p>
<code>Test(can=[Has('food'=True), Has('lamp'=True)])</code> -- Another way of doing the same thing.
</p>
<p>
<code>Test(can=Has(win=True), excludes=AteMushroom)</code> -- Look at the list of states which have <code>win</code>; make sure <em>none</em> of them passed through the <code>AteMushroom</code> action.
</p>
<h3>Pitfalls</h3>
<p>
Some things I've run into which tripped me up:
</p>
<p>
<strong>Don't create self-cancelling actions.</strong> Consider this action:
</p>
<pre>
MakeMoney = Chain(Lose('coin'), Set(coin=True, jewel=True))
</pre>
<p>
This ought to mean that if you have a coin, you also get a jewel. However, writing it this way -- removing and then setting the <code>coin</code> property in the same action -- confuses the optimizer. PlotEx won't recognize this as a strict improvement action, so you'll get the wrong answer. I think. It'll be less efficient, anyhow. Write it this way instead:
</p>
<pre>
MakeMoney = Chain(Has(coin=True), Set(jewel=True))
</pre>
<p>
<strong>Be careful about "recharge" actions.</strong> Say you have this scenario:
</p>
<pre>
Start = State(coin=True)
EnterShop = Once(Set(inshop=True))
BuyInShop = Chain(
Lose('inshop', 'coin'),
Set(fireball=True))
</pre>
<p>
This is fine; you start with a coin, you can enter the shop (just once), you buy a fireball with your coin. (Buying and leaving the shop are stuffed into the same action, for simplicity.) Run this, and PlotEx will correctly declare that the final state looks like:
</p>
<pre>
*<fireball _did_entershop>
(2): EnterShop, BuyInShop
</pre>
Later, you decide you want the player to be able to buy <em>two</em> fireballs, perhaps after locating a Fez of Awesome Charisma. But you don't want to mess with the existing <code>fireball</code> property. So you decide to patch around it, giving the player a sort of coupon that can be redeemed for a new fireball at any time:
<pre>
Start = State(coin=True)
FindFez = Once(Set(fez=True))
EnterShop = Once(Set(inshop=True))
BuyInShop = Chain(
Lose('inshop', 'coin'),
Set(fireball=True))
BuyInShopExtra = Chain(
Has(fez=True),
Lose('inshop', 'coin'),
Set(fireball=True, coupon=True))
MoreFire = Chain(Lose('coupon'), Set(fireball=True))
</pre>
<p>
Now the final state looks like:
</p>
<pre>
*<fez fireball _did_entershop _did_findfez>
(3): EnterShop, BuyInShop, FindFez
</pre>
<p>
There's no trace of the extra fireball! Or the coupon, or the <code>BuyInShopExtra</code> action. What just happened?
</p>
<p>
This setup falls prey to PlotEx's desire to only show us <em>final</em> states. If you run with the <code>-a</code> switch, you'll see that the <code>BuyInShopExtra</code> action <em>is</em> tried. But the notional player then gets impatient and turns in the coupon (<code>MoreFire</code>) because there's nothing else to do. This sets <code>fireball</code> true, but <code>fireball</code> is already true. So we're back in the state listed above -- but by a less efficient route. PlotEx doesn't bother showing that path, because it's already shown the shorter one.
</p>
<p>
Note that this is a display problem, not a failure to find the state. If you add an action sequence that requires two fireballs, PlotEx will correctly steer through it, using <code>MoreFire</code> to reload at the right point. However, when I use PlotEx, I spend a lot of time looking at intermediate states and partial win conditions. So it's worth avoiding these situations, where the player stops halfway and appears to be missing a possibility.
</p>
<p>
How to avoid? Use a numeric quality, instead of a "got it" flag and a "reload" flag.
</p>
<pre>
Start = State(coin=True)
FindFez = Once(Set(fez=True))
EnterShop = Once(Set(inshop=True))
BuyInShop = Chain(
Lose('inshop', 'coin'),
Increment('fireball'))
BuyInShopExtra = Chain(
Has(fez=True),
Lose('inshop', 'coin'),
Increment('fireball'), Increment('fireball'))
</pre>
<p>
(Yeah, I need to add a step-by argument to the <code>Increment</code> action class.) This produces two final outcomes, a much clearer rendering of the situation:
</p>
<pre>
<fez fireball=1 _did_entershop _did_findfez>
(3): EnterShop, BuyInShop, FindFez
*<fez fireball=2 _did_entershop _did_findfez>
(3): EnterShop, FindFez, BuyInShopExtra
</pre>
<hr>
<p>
Last updated June 3, 2012.
</p>
<p>
<em><a href="index.html">Learn why PlotEx exists</a></em>
</p>
<p>
<em><a href="../home.html">Zarfhome</a></em>
<em><a href="../sitemap.html#plotex">(map)</a></em>
<em><a href="../cave/plotex.html">(down)</a></em>
</p>
</body>
</html>