miserjs 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,750 +1 @@
1
- /**
2
- * I have intentionally used the most primitive types and functions in order to
3
- * closely match the original Commodore PET source code.
4
- *
5
- * There are lots of opportunities for modernizing or optimizing the code in
6
- * many different languages, including JavaScript here.
7
- *
8
- * I thought it best to keep it simple so this code could be ported to
9
- * non-object-oriented languages such as C, possibly on something as small as an
10
- * Arduino. It also makes it easier for someone new to programming to add new
11
- * rooms, floors, or objects.
12
- *
13
- */
14
- export default class MiserJSEngine{
15
- /**
16
- * This is where the response text is built, line by line, possibly from multiple methods.
17
- * Usually there is some text followed by output from the LOOK command.
18
- * @type {string}
19
- */
20
- #t="";
21
- /**
22
- * @type {MiserState}
23
- */
24
- #e;
25
- /**
26
- * Verb.
27
- * @type {string|null} First word in input: the verb.
28
- */
29
- #s=null;
30
- /**
31
- * Object.
32
- * @type {string|null} Second word in input: the object. Can be used later, as with the SAY verb.
33
- */
34
- #r=null;
35
- /**
36
- * If the input is greater than #INPUT_STRING_LIMIT characters, trim it.
37
- * Change this if you add new commands or object names that would exceed this length.
38
- * @type {number} To limit how much of the input string is subject to the tokenizer. */
39
- static#i=40;
40
- /**
41
- *
42
- * @param {MiserState|null} miserState
43
- */
44
- constructor(t=null){
45
- // ToDo: Check for a valid MiserState object.
46
- this.#e=t||JSON.parse(JSON.stringify(MiserJSEngine.#n))}newGame(){
47
- // Deep copy the default state.
48
- this.#e=JSON.parse(JSON.stringify(MiserJSEngine.#n))}
49
- /**
50
- * The primary method for playing the game.
51
- * @param {string} input Player input text.
52
- * @returns {MiserResponse}
53
- */request(t){this.#t="";
54
- /**
55
- * Simple argument checking here, since you will be using
56
- * this engine behind your own front-end anyway.
57
- * Do more strict player input sanitization there.
58
- * You'll probably want to parse the player input in your front-end and
59
- * add some special commands like 'save', 'load', etc.
60
- */
61
- let e=t;if(!e)return this.#o("No input was provided.",!1,!0);
62
- // Make sure leading and trailing white-space is deleted.
63
- e=e.trim(),
64
- /**
65
- * If the inputString is greater than #INPUT_STRING_LIMIT characters,
66
- * trim it, to minimize the work done in the tokenizer below.
67
- * Change this (above) if you add new commands or object names that would exceed this length.
68
- * Default value: 40 characters for a "verb object" input line.
69
- */
70
- e.length>MiserJSEngine.#i&&(e=e.substring(0,MiserJSEngine.#i))
71
- /**
72
- * Simple tokenizer to get the words.
73
- */;let s,r,i=0,n=0,o=[];
74
- // The following will remove leading spaces, spaces between words, and trailing spaces.
75
- do{" "!==e.charAt(i)?i++:(i>n&&o.push(e.substring(n,i)),i++,n=i)}while(i<e.length);
76
- // Get the final word, which might be the entire string if there were no spaces in it.
77
- // JavaScript substring does not include the character at the end index (stringIndex here).
78
- switch(i>n&&o.push(e.substring(n,i)),o.length){case 1:
79
- // Search the verbs list
80
- return s=this.#a(o[0]),s>0?(this.#s=o[0],this.#h(s,0)):this.#u();case 2:return s=this.#a(o[0]),s>0?(this.#s=o[0],r=this.#p(o[1]),r>0?(this.#r=o[1],this.#h(s,r)):this.#u()):this.#u();default:return this.#o("Please type a one or two word command.\n")}}
81
- /**
82
- * Implements the BASIC code at lines 900-920.
83
- * @param {number} i
84
- * @param {number} j
85
- */#h(t,e){switch(t){case 1:case 2:return this.#l(e);case 3:case 4:case 5:return this.#c(e);case 6:return this.#m(e);case 7:return this.#S(e);case 8:case 29:return this.#d();case 9:return this.#g();case 10:return this.#f(e);case 11:return this.#T(e);case 12:return this.#y(e);case 13:return this.#w(e);case 14:return this.#b(e);case 15:return this.#x();case 16:return this.#k(e);case 17:case 18:return this.#v();case 19:case 20:return this.#I();case 21:case 22:return this.#E();case 23:case 24:return this.#P();case 25:return this.#M();case 26:return this.#Y(e);case 27:return this.#R();case 28:return this.#J();
86
- // Case 29, INVENTORY, handled above at case 8,29.
87
- case 30:return this.#O(e);default:throw new Error(`In Action method: Verb '${this.#s}' was defined in the verbs array, but not implemented yet.`)}}
88
- /**
89
- * Case 1 and 2: Get, Take
90
- * @param {number} j Index into objects array.
91
- * @returns {MiserResponse}
92
- */#l(t){if(0==t)return this.#u();if(-1==this.#e.pt[t])return this.#o("I am unable to do that.\n");if(-1==this.#G(t))return this.#o("You're already carrying it.\n");if(this.#G(t)!=this.#e.cp)return this.#N();this.#e.ol[this.#e.pt[t]]=-1,this.#t+="Ok\n";
93
- // Line 1030
94
- let e=this.#e.pt[t];return e>3&&e<9||19===e?(this.#e.gt+=1,this.#t+="You got a treasure!\n",this.#o(this.#t)):(2===t&&-2===this.#e.ol[20]&&(
95
- // Sets key location to Front Porch.
96
- this.#e.ol[20]=0,this.#t+="You find a door key!\n"),this.#o(this.#t))}
97
- /**
98
- * Case 3,4,5: Move, Slide, Push
99
- * This method implements the functionality of lines 2000-2210 in the original Miser program.
100
- * @param {number} j Index into objects array.
101
- * @returns {MiserResponse}
102
- */#c(t){if(0==t)
103
- // Prints the "What?" or "I don't understand that." messages.
104
- // Lines 210 and 50000 in the original 1981 Miser program.
105
- return this.#u();
106
- // Check the pt[] array for a -1, which means this object doesn't move.
107
- // Skip the check for the cabinet pt[13], since it can be moved conditionally. Checked after this in the switch.
108
- if(13!=t){if(-1==this.#e.pt[t])return this.#o("That item stays put.\n");if(this.#e.ol[this.#e.pt[t]]!=this.#e.cp&&-1!=this.#e.ol[this.#e.pt[t]])
109
- // "I don't see it here."
110
- return this.#N()}
111
- // Only the CABINET, MAT, and RUG can move.
112
- switch(t){
113
- // Mat
114
- case 2:
115
- // If brass door key not found/hidden (-2).
116
- if(-2==this.#e.ol[20])
117
- // Set object location (ol[20]) of key (20) to the front porch (rString[0]).
118
- return this.#e.ol[20]=0,this.#o("You find a door key!\n");break;
119
- // Oriental Rug
120
- case 10:
121
- // If trapdoor not found/hidden (-2)
122
- if(-2==this.#e.ol[16])
123
- // Found trapdoor. Location is now in the Formal Parlor, so it will be observed on a new LOOK command.
124
- return this.#t+="You find a trap door!\n",this.#e.ol[16]=6,this.#x();break;
125
- // Cabinet
126
- case 13:
127
- // CP=5=Red-Walled Room rString[5]
128
- // rPercent(5,3)=0 means EAST direction is unavailable, meaning the vault has not been found yet.
129
- // So this means, 'If in Red-Walled Room and EAST direction unavailable, move the cabinet and find the vault'.
130
- return 5==this.#e.cp&&0==this.#e.rPercent[5][3]?(
131
- // fv is Found Vault: Set it to true.
132
- this.#e.fv=!0,this.#t+="Behind the cabinet is a vault!\n",this.#x()):this.#o("That item stays put.\n")}return this.#o("Moving it reveals nothing.\n")}
133
- /**
134
- * CASE 6: Open.\
135
- * Lines 4000-4260 in the original Miser program from 1981.
136
- * The only objects that have a response for open are:
137
- * Valve (7), Book (11), Door (12), Cabinet (13),
138
- * Organ (16), Bag (22), and Vault (27).
139
- * @param {number} j Index into objects array.
140
- * @returns {MiserResponse}
141
- */#m(t){switch(t){case 0:
142
- // Nothing to open.
143
- return this.#u();case 7:
144
- // Valve.
145
- return this.#o("Try turning it.\n");case 11:
146
- // Book.
147
- if(this.#e.ol[this.#e.pt[t]]==this.#e.cp||-1==this.#e.ol[this.#e.pt[t]])return this.#o("Scrawled in blood on the inside front cover is the message, \"'Victory' is a prize-winning word\".\n");break;case 12:
148
- // Door.
149
- switch(this.#e.cp){case 0:
150
- // Front Porch.
151
- // Door unlocked?
152
- return this.#e.du?this.#o("It's already open.\n"):this.#o("Sorry, the door is locked.\n");case 6:
153
- // Formal Parlor.
154
- return this.#t+="You open the door. You lean over to peer in, and you fall in!\n",this.#e.cp=47,this.#x()}return this.#N();
155
- // Cabinet
156
- case 13:
157
- // In Red-Walled room? (CP=5)
158
- return this.#e.ol[26]==this.#e.cp?this.#o("The cabinet is empty and dusty.\nScribbled in dust on one shelf are the words, 'behind me'.\n"):this.#N();
159
- // Organ
160
- case 16:
161
- // In Ballroom?
162
- return 21==this.#e.cp?this.#e.gg?-2!=this.#e.ol[24]?(
163
- // Hide the 'ORGAN IN THE CORNER' and reveal the 'OPEN ORGAN IN THE CORNER'
164
- this.#e.ol[24]=-2,this.#e.ol[25]=21,
165
- // Reveal the Parachute Ripcord
166
- this.#e.ol[17]=21,
167
- // Reveal the Ruby Slippers
168
- this.#e.ol[19]=21,this.#t+="As you open it, several objects suddenly appear!\n",this.#x()):this.#o("It's already open.\n"):this.#o("It's stuck shut.\n"):this.#N();
169
- // Bag
170
- case 22:
171
- // If in the vault or carrying it
172
- return this.#e.ol[this.#e.pt[t]]==this.#e.cp||-1==this.#e.ol[this.#e.pt[t]]?this.#o("The bag is knotted securely.\nIt won't open.\n"):this.#N();
173
- // Vault
174
- case 27:
175
- // If in Red-Walled Room and FV=true (found vault)
176
- return 5==this.#e.cp&&this.#e.fv?
177
- // Vault open?
178
- this.#e.vo?this.#o("It's already open.\n"):this.#o("I can't, it's locked.\n"):this.#N()}return this.#o("I don't know how to open that.\n")}
179
- /**
180
- * CASE 7: Read.
181
- * Lines 5000-5050 in the original Miser program from 1981.
182
- * @param {*} j Index into objects array.
183
- * @returns
184
- */#S(t){
185
- // Second word to act on?
186
- if(0==t)
187
- // Returns 'What?' or 'I don't understand that.'.
188
- return this.#u();if(-1==this.#e.pt[t])return this.#o("There's nothing written on that.\n");if(!this.#j(t))
189
- // Returns "I don't see it here."
190
- return this.#N();switch(t){
191
- // PAPER
192
- case 3:
193
- // Player now knows the combination to the vault
194
- return this.#e.kc=!0,this.#o("It says, '12-35-6'.\nhmm.. looks like a combination.\n");
195
- // BOOK
196
- case 11:return this.#o("The front cover is inscribed in Greek.\n");default:return this.#o("There's nothing written on that.\n")}}
197
- /**
198
- * Case 8,29: Inventory.
199
- * Output a list of all objects the player is carrying.
200
- * Lines 6000-6040 in the original Miser program from 1981.
201
- * @returns {MiserResponse}
202
- */#d(){this.#t+="You are carrying the following:\n\n";let t=0;for(let e=1;e<28;e++)
203
- // Carrying object omString[x]?
204
- if(-1==this.#e.ol[e]){
205
- // Bucket full?
206
- if(t=1,this.#t+=`${MiserJSEngine.#D[e]}\n`,1==e&&this.#e.bf){this.#t+=" The bucket is full of water.\n";continue}14==e&&(this.#t+=" (Better fix it)\n")}
207
- // Found items?
208
- return 1==t?this.#o(this.#t):this.#o(this.#t+="Nothing at all.\n")}
209
- /**
210
- * Case 9: Quit.
211
- * Lines 7000-7150 in the original Miser program from 1981.
212
- *
213
- * Special actions for quit should be for the host program to decide.
214
- * The host could use the method, this.#showFinalOutcome or combine it with a
215
- * score display, or something else.
216
- * @returns {MiserResponse}
217
- */#g(){
218
- // Take special action for quit in the host program.
219
- // The host could use the method, this.showFinalOutcome, or combine it with a score display, or something else.
220
- return this.#o(this.#t,MiserJSEngine.#A.QUIT)}
221
- /**
222
- * Case 10: Drop.
223
- * Lines 8000-8221 in the original Miser program from 1981.
224
- * @param {number} j Index into objects array.
225
- * @returns {MiserResponse}
226
- */#f(t){
227
- // Preservation of bug in original code
228
- if(-1==this.#e.pt[t])return this.#t+="\n?ILLEGAL QUANTITY ERROR IN 8000\n\n",this.#t+="----------------------------------------",this.#t+="** You have encountered a bug that\n",this.#t+="existed in the original Miser program\n",this.#t+="from 1981. **\n\n",this.#t+="This bug is being reproduced here to\n",this.#t+="preserve the experience a player would\n",this.#t+="have had playing this game in 1981 on\n",this.#t+="one of the Commodore PET computers.\n\n",this.#t+="Except for one thing...the game doesn't\n",this.#t+="just end unexpectedly here as it did\n",this.#t+="back then.\n\n",this.#t+="You get to keep playing as if nothing\n",this.#t+="bad happened!\n",this.#o(this.#t);
229
- // Carrying this object?
230
- if(-1!=this.#e.ol[this.#e.pt[t]])return this.#o("You aren't carrying it!\n");
231
- // Ok, player is carrying the object represented by pt[j], since ol[pt[j]] has a value of -1 here.
232
- // The only objects that DROP should provide a special response for are the 5 treasures, the Penny, and the Cross.
233
- // All other objects just get dropped with an 'Ok' response.
234
- // Check for one of the 5 treasures
235
- switch(this.#e.pt[t]){
236
- // One of the 5 treasures
237
- // Remember that the value of pt[j] is an index into the omString array (om$() in original program).
238
- case 4:case 5:case 7:case 8:case 19:return this.#o("Don't drop *treasures*!\n")}
239
- // Check for a Penny or a Cross specifically. All other objects should just be dropped with a response of "Ok"
240
- switch(t){
241
- // Drop Penny
242
- case 19:
243
- // In Portico?
244
- if(19==this.#e.cp)
245
- // The Penny is being carried, and the player is in the Portico.
246
- // DROP PENNY action
247
- // Update the EAST (3) direction of the BALLROOM (21) to point toward the CHAPEL (22)
248
- // Player can now move from the BALLROOM to the CHAPEL.
249
- // Modifies rPercent array.
250
- return this.#e.rPercent[21][3]=22,
251
- // Update object location of the PENNY to 'hidden'(-2)
252
- this.#e.ol[12]=-2,this.#o("As the penny sinks below the surface of the pool, a fleeting image of a chapel with dancers outside appears.\n");break;
253
- // Drop Cross.
254
- case 20:
255
- // In Chapel?
256
- if(22==this.#e.cp)
257
- // "chapel. A tablet says 'drop a religious item or die!!' becomes simply "chapel".
258
- // rString array was directly modified here in the original source code.
259
- // This is now handled as a special case in the LOOK command, when in the chapel (index 22 in rString).
260
- // That way we can keep the rString array as static data that never changes, doesn't have to be saved/restored.
261
- // DON'T USE NOW: this.#miserState.rString[22] = "chapel";
262
- // "organ in the corner" becomes "closed organ playing music in the corner"
263
- // omString array was directly modified here in the original source code.
264
- // This is now handled as a special case in the LOOK command, when in the ballroom.
265
- // That way we can keep the omString array as static data that never changes, doesn't have to be saved/restored.
266
- // DON'T USE NOW: this.#miserState.omString[24] = "closed organ playing music in the corner";
267
- // The CROSS is being carried, and the player is in the Chapel.
268
- // DROP CROSS action
269
- // LINES 8200-8221 in the original program.
270
- // Still trying to figure out what GG could stand for.
271
- // If GG is TRUE it means the player can OPEN the ORGAN in the BALLROOM.
272
- // Prior to dropping the CROSS here, this wasn't possible.
273
- // GG = 'Got God'? You know...chapel...cross...God...
274
- return this.#e.gg=!0,
275
- // RUSTY CROSS becomes 'hidden' (-2)
276
- this.#e.ol[11]=-2,this.#o("Even before it hits the ground, the cross fades away!\n\nThe tablet has disintegrated.\n\nYou hear music from the organ.\n")}
277
- // If the PENNY or the CROSS weren't in their special action locations in the above switch statement, they will be dropped here.
278
- // Any other objects being carried will be dropped here as well.
279
- // ol[pt[j]] (object location) gets updated to the current position. (CP)
280
- return this.#e.ol[this.#e.pt[t]]=this.#e.cp,this.#o("Ok\n")}
281
- /**
282
- * Case 11: Say.
283
- * Lines 9000-9300 in the original Miser program from 1981.
284
- * @param {number} j Index into objects array.
285
- * @returns {MiserResponse}
286
- */#T(t){switch(t){
287
- // No second word to say
288
- case 0:return this.#o("Say what???\n");
289
- // Lines 9100-9120 Say ritnew
290
- case 14:
291
- // In Pantry?
292
- return 4==this.#e.cp?
293
- // Charmed Snake?
294
- this.#e.ch?this.#o("Nothing happens.\n"):(this.#e.ch=!0,
295
- // Vicious snake disappears from conservatory.
296
- this.#e.ol[2]=-2,
297
- // Charmed Snake appears in conservatory. (rString[4])
298
- this.#e.ol[3]=4,this.#o("The snake is charmed by the very utterance of your words.\n")):this.#o("Nothing happens.\n");
299
- // Lines 9200-9220 Say victory
300
- case 15:
301
- // In Trophy room?
302
- return 8==this.#e.cp?
303
- // Portal open?
304
- this.#e.po?this.#o("Nothing happens.\n"):(
305
- // Set Portal Open
306
- this.#e.po=!0,
307
- // Set Trophy Room north direction to point toward rString[17], which is the Game Room.
308
- // Modifies rPercent array.
309
- this.#e.rPercent[8][1]=17,
310
- // 'Portal in the North Wall' appears in the Trophy Room (rString[8]).
311
- this.#e.ol[18]=8,this.#o("A portal has opened in the north wall!!\n")):this.#o("Nothing happens.\n");
312
- // Line 9300 Say Xyzzy, Say Plugh (These are magic words used in the 'Colossal Cave Adventure' game from 1975!)
313
- case 29:case 30:return this.#o("A hollow voice says, 'Wrong adventure'.\n");default:return t<29?this.#o(`Okay, '${this.#r}'.\nNothing happens.\n`):this.#u()}}
314
- /**
315
- * CASE 12: Pour.
316
- * Lines 10000 to 10060 in the original Miser program from 1981.
317
- * @param {number} j Index into objects array.
318
- * @returns {MiserResponse}
319
- */#y(t){
320
- // Only the bucket (oString[4]) can be poured.
321
- if(4!=t)return this.#o("I wouldn't know how.\n");if(!this.#j(t))return this.#N();
322
- // Is bucket empty?
323
- if(!this.#e.bf)return this.#o("The bucket is already empty.\n");switch(this.#e.cp){
324
- // BLUE DRAWING ROOM
325
- case 10:
326
- // Is fire burning?
327
- if(this.#e.fb)
328
- // Fire Burning becomes FALSE. The fire is out now.
329
- return this.#e.fb=!1,
330
- // Bucket is no longer full.
331
- this.#e.bf=!1,this.#t+="Congratulations! You have vanquished the flames.\n",this.#x();break;
332
- // PORTICO
333
- case 19:return this.#o("Ok\n");
334
-
335
- }return this.#o("The water disappears quickly.\n")}
336
- /**
337
- * CASE 13: Fill.
338
- * Lines 11000-11070 in the original Miser program from 1981.
339
- * @param {number} j Index into objects array.
340
- * @returns {MiserResponse}
341
- */#w(t){if(0==t)
342
- // "What?" or "I don't understand that."
343
- return this.#u();if(-1==this.#e.pt[t])return this.#o("That wouldn't hold anything.\n");if(!this.#j(t))
344
- // "I don't see it here."
345
- return this.#N();
346
- // Is this the bucket?
347
- if(4==t){
348
- // Is the bucket full?
349
- if(this.#e.bf)return this.#o("It's already full.\n");switch(this.#e.cp){
350
- // Bucket can ony be filled in the PORTICO and BACK YARD near the faucet.
351
- case 19:case 23:return this.#e.bf=!0,this.#o("Your bucket is now full.\n");
352
- // POOL AREA
353
- case 25:if(this.#e.pf)return this.#o("I'd rather stay away from the mercury.\n")}return this.#o("I don't see any water here.\n")}
354
- // Only the bucket can be filled
355
- return this.#o("That wouldn't hold anything.\n")}
356
- /**
357
- * Unlock.
358
- * Only the DOOR, TRAPDOOR, and VAULT are acted on here.
359
- * This method implements the functionality of lines 11000 to 11070
360
- * in the original Miser program from 1981.
361
- * @param {number} j Index into objects array.
362
- * @returns {MiserResponse}
363
- */#b(t){
364
- // Only the DOOR/TRAPDOOR (12) and the VAULT (27) can be unlocked
365
- switch(t){
366
- // No object
367
- case 0:
368
- // Return "What?" or "I don't understand that."
369
- return this.#u();
370
- // Door or Trapdoor
371
- case 12:switch(this.#e.cp){
372
- // Door at Front Porch (CP=0)
373
- case 0:return this.#e.du?this.#o("It's already unlocked.\n"):
374
- // Carrying the key? (omString[20])
375
- -1!=this.#e.ol[20]?this.#o("I need a key.\n"):(this.#e.du=!0,this.#t+="The door easily unlocks and swings open.\n",this.#x());
376
- // Trapdoor in Formal Parlor (CP=6)
377
- case 6:
378
- // Trapdoor Hidden?
379
- return-2!=this.#e.ol[16]?this.#o("The trapdoor has no lock.\n"):this.#o("I don't see it here.\n");default:
380
- // "I don't see it here."
381
- return this.#N()}
382
- // Vault
383
- case 27:
384
- // In Red-Walled Room?
385
- return 5==this.#e.cp?
386
- // Vault open?
387
- this.#e.vo?this.#o("It's already open.\n"):
388
- // Found vault?
389
- this.#e.fv?
390
- // Know combination?
391
- this.#e.kc?(this.#e.vo=!0,
392
- // Modifies rPercent array.
393
- this.#e.rPercent[5][3]=46,this.#t+="Ok, let's see. 12..35..6..\n<CLICK!> The door swings open.",this.#x()):this.#o("I don't know the combination.\n"):this.#N():this.#N();default:return this.#o("I wouldn't know how to unlock one.\n")}}
394
- /**
395
- * CASE 15: Look.
396
- * Lines 14000-14170 in the original Miser program from 1981.
397
- * @returns {MiserResponse}
398
- */#x(){
399
- // Line 14000 - Print current position
400
- let t=this.#e.cp;22==t&&this.#e.gg?
401
- // If in chapel, and already dropped the cross, print a different room description.
402
- this.#t+="\nYou are in the chapel.\n":this.#t+=`\nYou are in the ${MiserJSEngine.#B[t]}.\n`;
403
- // Lines 14010-14030 Print list of all objects at this location (CP variable)
404
- for(let e=1;e<29;e++)this.#e.ol[e]==t&&(
405
- // Object found at this location (CP).
406
- // Special case for the organ.
407
- 24==e&&this.#e.gg?this.#t+="\nThere is a closed organ playing music in the corner here.\n":this.#t+=`\nThere is a ${MiserJSEngine.#D[e]} here.\n`,
408
- // If the plastic bucket is here and it is full (BF=true).
409
- 1==e&&this.#e.bf&&(this.#t+=" The bucket is full of water.\n"));
410
- // Special actions depending on current position (CP)
411
- switch(this.#e.cp){case 0:
412
- // Line 14127 Front Porch
413
- // Door unlocked?
414
- this.#e.du&&(this.#t+="\nAn open door leads north.\n");break;case 5:
415
- // Lines 14125-14126 Red-Walled Room
416
- // Found vault?
417
- this.#e.fv&&(this.#t+="\nThere is a vault in the east wall.\n"),this.#e.vo&&(this.#t+="The vault is open.\n");break;case 10:
418
- // Lines 14060-14080 and 14120 Blue Drawing Room
419
- this.#e.fb?
420
- // Fire Burning
421
- this.#t+="\nThere is a hot fire on the south wall!\nIf I go that way I'll burn to death!\n":this.#t+="\nThere is evidence of a recent fire here.\n";break;case 16:
422
- // Lines 14090-14105 Pantry
423
- this.#t+="\nA rich, full voice says, 'Ritnew is a charming word'.\n";break;case 23:
424
- // Line 14115 Back Yard
425
- this.#t+="\nThere is a leaky faucet nearby.\n";break;case 25:
426
- // Lines 14040-14056 Pool Area
427
- // Pool full?
428
- this.#e.pf?this.#t+="\nThe pool is full of liquid mercury!\n":(this.#t+="\nThe pool's empty.\n",48==this.#e.ol[7]&&(this.#t+="\nI see something shiny in the pool!\n"));break;case 26:
429
- // Line 14110 Pump House
430
- this.#t+="\nThere is a valve on one of the pipes.\n";break;case 48:
431
- // Line 14130 Bottom of Swimming Pool
432
- // Skip showing obvious exits.
433
- return this.#o(this.#t)}
434
- // Lines 14130-14170
435
- // Print all available direction commands from this location.
436
- return this.#t+="\nObvious Exits:\n",this.#e.rPercent[this.#e.cp][1]>0&&(this.#t+="N "),this.#e.rPercent[this.#e.cp][2]>0&&(this.#t+="S "),this.#e.rPercent[this.#e.cp][3]>0&&(this.#t+="E "),this.#e.rPercent[this.#e.cp][4]>0&&(this.#t+="W "),
437
- // Add newline.
438
- this.#t+="\n",this.#o(this.#t)}
439
- /**
440
- * CASE 16: Go.
441
- * Lines 15000-15080 in the original Miser program from 1981.
442
- * @param {number} j Index into objects array.
443
- * @returns {MiserResponse}
444
- */#k(t){
445
- // Valid objects are Ladder(8), Stairs(18), and Pool(28).
446
- switch(t){
447
- // Ladder
448
- case 8:
449
- // In bottom of pool?
450
- return 48==this.#e.cp?(
451
- // Move from 'bottom of swimming pool' to 'Pool Area'
452
- this.#e.cp=25,this.#x()):this.#N();
453
- // Stairs
454
- case 18:
455
- // In 'Great Hall' or 'Middle of the western hallway'?
456
- switch(this.#e.cp){
457
- // Great Hall.
458
- case 2:
459
- // Carrying sword?
460
- return-1==this.#e.ol[9]?(
461
- // Move from Great Hall to 'Middle of the western hallway'
462
- this.#e.cp=27,this.#t+="The suits of armor try to stop you, but you fight them off with your sword.\n",this.#x()):this.#o("The suits of armor prevent you from going up!\n");
463
- // Middle of the western hallway.
464
- case 27:
465
- // Move to Great Hall
466
- return this.#e.cp=2,this.#x();default:
467
- // Print "I don't see it here."
468
- return this.#N()}
469
- // Pool
470
- case 28:
471
- // Pool full?
472
- return this.#e.pf?this.#o("The pool is full of mercury!\n"):(
473
- // Move to bottom of swimming pool
474
- this.#e.cp=48,this.#x());default:return this.#u()}}
475
- /**
476
- * CASE 17,18: North.
477
- * Lines 16000-16020 in the original Miser program from 1981.
478
- * @returns {MiserResponse}
479
- */#v(){return 0!=this.#e.cp||this.#e.du?0==this.#e.rPercent[this.#e.cp][1]?this.#C():(0==this.#e.cp&&(this.#t+="\nThe door slams shut behind you!\n"),this.#e.cp=this.#e.rPercent[this.#e.cp][1],this.#x()):(this.#t+="The door is locked shut.\n",this.#o(this.#t))}
480
- /**
481
- * CASE 19,20: South.
482
- * Lines 17000 to 17050 in the original Miser program from 1981.
483
- * @returns {MiserResponse}
484
- */#I(){if(10==this.#e.cp&&this.#e.fb)return this.#t+="You have burnt to a crisp!\n",this.#o(this.#t,MiserJSEngine.#A.DIED);const t=this.#H();return null!=t?t:0==this.#e.rPercent[this.#e.cp][2]?this.#C():(this.#e.cp=this.#e.rPercent[this.#e.cp][2],this.#x())}
485
- /**
486
- * CASE 21,22: East.
487
- * There is a check for the snake every time the player tries to move East,
488
- * regardless of the current position.
489
- * Lines 17010 to 17050 in the original Miser program from 1981.
490
- * @returns {MiserResponse}
491
- */#E(){const t=this.#H();return null!=t?t:0==this.#e.rPercent[this.#e.cp][3]?this.#C():(this.#e.cp=this.#e.rPercent[this.#e.cp][3],this.#x())}
492
- /**
493
- * CASE 23,24: West.
494
- * Lines 19000 to 19010 in the original Miser program from 1981.
495
- * @returns {MiserResponse}
496
- */#P(){return 0==this.#e.rPercent[this.#e.cp][4]?this.#C():(this.#e.cp=this.#e.rPercent[this.#e.cp][4],this.#x())}
497
- /**
498
- * CASE 25: Score.
499
- * Lines 20000 to 20060 in the original Miser program from 1981.
500
- * @returns {MiserResponse}
501
- */#M(){this.#t+=`Your current score is: ${20*this.#e.gt} points.\n(100 possible)\n`;let t=this.#e.gt;this.#e.es&&t++,this.#t+=`\nYour rank is: ${MiserJSEngine.#$[t]}\n`;let e=MiserJSEngine.#$.length-1-t;return this.#t+=0==e?" (You have escaped with ALL available treasure!)\n":1==e?" (There is only one rank left for you to achieve!)\n":` (There are ${e} more ranks you can achieve.)\n`,this.#o(this.#t)}
502
- /**
503
- * CASE 26: Turn.
504
- * Only the VALVE can be turned.
505
- * Lines 21000 to 21070 in the original Miser program from 1981.
506
- * @param {number} j Index into objects array.
507
- * @returns {MiserResponse}
508
- */#Y(t){return 7!=t?this.#o("I don't know how to turn such a thing.\n"):26!=this.#e.cp?this.#N():(
509
- // Toggle POOL FULL
510
- this.#e.pf=!this.#e.pf,this.#o("With much effort, you turn the valve 5 times. You hear the sound of liquid\nflowing through the pipes.\n"))}
511
- /**
512
- * CASE 27: Jump.
513
- * Can only JUMP from MIDDLE OF THE WESTERN HALLWAY, FRONT BALCONY, and REAR BALCONY.
514
- * Lines 22000 to 22540 in the original Miser program from 1981.
515
- * @returns {MiserResponse}
516
- */#R(){switch(this.#e.cp){case 27:
517
- // MIDDLE OF THE WESTERN HALLWAY
518
- return this.#t+="You jump...\n",this.#e.jm?(this.#t+="Now you've done it. You ignored\nmy warning, and as a result\nyou have broken your neck!\n\nYou are dead.",this.#o(this.#t,MiserJSEngine.#A.DIED)):(this.#e.jm=!0,this.#e.cp=2,this.#t+="You have landed down-stairs,\nand narrowly escaped serious\ninjury. Please don't try it again.\n",this.#x());case 29:case 32:
519
- // Next action depends on the three possible states of the parachute in the players inventory:
520
- // 1) No parachute. Not in inventory.
521
- // 2) Carrying the parachute that hasn't been fixed with the ripcord.
522
- // 3) Carrying a fully functional parachute.
523
- if(this.#t+="You jump...\n",-1==this.#e.ol[14])
524
- // Have Parachute with no ripcord.
525
- return this.#t+="There is no way to open the parachute!\n",this.#t+="You hit the ground.\n",this.#t+="You have broken your neck!\n\n",this.#t+="You are dead.",this.#o(this.#t,MiserJSEngine.#A.DIED);if(-1!=this.#e.ol[27])return this.#t+="You hit the ground.\n",this.#t+="You have broken your neck!\n\n",this.#t+="You are dead.",this.#o(this.#t,MiserJSEngine.#A.DIED);if(
526
- // Have fully functional parachute.
527
- this.#t+="You yank the ripcord and the\n'chute comes billowing out.\n",32==this.#e.cp)
528
- // At rear balcony, so change current position to HEDGE MAZE (40)
529
- return this.#e.cp=40,this.#x();if(29==this.#e.cp)return this.#t+="You land safely.\n\nCongratulations on escaping!\n",this.#e.es=!0,this.#o(this.#t,MiserJSEngine.#A.ESCAPED);default:return this.#t+="There's nowhere to jump.",this.#o(this.#t)}}
530
- /**
531
- * CASE 28: Swim.
532
- * Lines 24000 to 24030 in the original Miser program from 1981.
533
- * @returns {MiserResponse}
534
- */#J(){switch(this.#e.cp){case 19:
535
- // Portico.
536
- return this.#o("The water is only a few inches deep.\n");case 25:
537
- // Pool area.
538
- // Pool full?
539
- return this.#e.pf?this.#o("In mercury? No way!\n"):this.#o("The pool is empty.\n");default:return this.#o("There's nothing here to swim in!\n")}}
540
- // Case 29: INVENTORY -> Handled at Case 8 above (Case 8,29:)
541
- /**
542
- * CASE 30: Fix.
543
- * Lines 25000 to 25070 in the original Miser program from 1981.
544
- * @param {number} j Index into objects array.
545
- * @returns {MiserResponse}
546
- */
547
- #O(t){switch(t){case 0:
548
- // Nothing to fix
549
- return this.#u();case 7:
550
- // Valve
551
- return this.#o("I ain't no plumber.\n");case 17:
552
- // If parachute with no ripcord isn't at current position AND not carrying it
553
- return this.#j(t)?-2==this.#e.ol[14]?this.#o("It's already fixed.\n"):
554
- // If not carrying the ripcord
555
- -1!=this.#e.ol[17]?this.#o("I need a ripcord.\n"):(
556
- // Have parachute with no ripcord, and ripcord is here, so fix the parachute.
557
- // Reveal 'repaired parachute' at the same location as 'parachute with no ripcord'
558
- this.#e.ol[27]=this.#e.ol[14],
559
- // Hide 'parachute with no ripcord'
560
- this.#e.ol[14]=-2,
561
- // Update pointer to omString[27] 'repaired parachute'
562
- this.#e.pt[17]=27,
563
- // Update parachute ripcord location to the front porch. Weird, but it works to hide it.
564
- this.#e.ol[17]=0,this.#o("I'm no expert, but I think it'll work.\n")):this.#N();default:return this.#o("I wouldn't know how.\n")}}
565
- /**
566
- *
567
- * @returns {MiserResponse|null}
568
- */#H(){return 4!=this.#e.cp||this.#e.ch?null:this.#e.ps?this.#o("The snake bites you!\nYou are dead.\n",MiserJSEngine.#A.DIED):(this.#e.ps=!0,this.#o("The snake is about to attack!\n"))}
569
- /**
570
- * Call this method after player dies or escapes, to get the final score and rank.
571
- * @returns {MiserResponse}
572
- */showFinalOutcome(){this.#t=`\nYou accumulated ${this.#e.gt} treasures, \n for a score of ${20*this.#e.gt} points.\n(100 Possible)\n`;let t=this.#e.gt;return this.#e.es?t++:this.#t+="\nHowever, you did not escape.\n",this.#t+=`\nThis puts you in a class of:\n${MiserJSEngine.#$[t]}\n`,6!=t&&(this.#t+="\nBetter luck next time!\n"),this.#o(this.#t,!0)}
573
- /**
574
- * Creates new output arguments.
575
- * @param {string} outputText String to return.
576
- * @param {boolean|string} [gameOver=false]
577
- * @param {boolean} [isError=false] Signal an error to the host program.
578
- * @returns {MiserResponse}
579
- */#o(t,e=!1,s=!1){return{text:t,gameOver:e,isError:s}}
580
- /**
581
- * Line 810 in original program from 1981.
582
- * @param {string} s
583
- * @returns {number} Index into verbs[] array, or 0 if not found.
584
- */#a(t){(t=t.toLowerCase()).length>4&&(t=t.substring(0,4))
585
- /*
586
- This can be done more efficiently in most languages, but this is how
587
- it's done in the original 1981 program at Line 810.
588
- */;for(let e=1;e<MiserJSEngine.#z.length;e++)if(t===MiserJSEngine.#z[e])
589
- // Match. Return array index into the verbs array.
590
- return e;return 0}
591
- /**
592
- * @param {string} s
593
- * @returns {number} Index into objects[] array, or 0 if not found.
594
- */#p(t){(t=t.toLowerCase()).length>4&&(t=t.substring(0,4));for(let e=1;e<MiserJSEngine.#F.length;e++)if(t===MiserJSEngine.#F[e])
595
- // Match. Return array index into the objects array.
596
- return e;return 0}
597
- /**
598
- * Returns one of "What?" or "I don't understand that."
599
- * @returns {MiserResponse}
600
- */#u(){return this.#t+=`${MiserJSEngine.#L[this.#e.em]}\n`,
601
- // Alternate between hString[1] and hString[2].
602
- this.#e.em=3-this.#e.em,this.#o(this.#t)}
603
- /**
604
- * Returns "I don't see it here."
605
- * @returns {MiserResponse}
606
- */#N(){return this.#t+="I don't see it here.\n",this.#o(this.#t)}
607
- /**
608
- * Line 52000 in the original 1981 program.
609
- * Tried to go in a direction not defined in r%(room number, N, S, E, W)
610
- * Returns "It's impossible to go that way."
611
- * @returns {MiserResponse}
612
- */#C(){return this.#t+="It's impossible to go that way.\n",this.#o(this.#t)}
613
- /**
614
- * If object is at current position (cp) or being carried (-1), it is present.
615
- * In the 1981 source code, this check is done at multiple locations, using
616
- * the same line of code:
617
- * Lines 2020, 4005, 4170, 5005, 11020, 25030
618
- * @param {number} j
619
- * @returns {boolean}
620
- */#j(t){return this.#G(t)==this.#e.cp||-1==this.#G(t)}
621
- /**
622
- * Returns an object location value for the object typed after the verb on an input line.
623
- * @param {number} x Index into objects array.
624
- */#G(t){return this.#e.ol[this.#e.pt[t]]}getCurrentPosition(){return this.#e.cp}
625
- /**
626
- * Get a more concise name for the current room/location listed in the rString[] array.
627
- * @param {number} cp Current position from a MiserState object.
628
- * @returns {string}
629
- */getCurrentPositionShortName(t){return MiserJSEngine.#W[t]}
630
- /**
631
- *
632
- * @param {MiserState} miserState
633
- */getInventoryItemCount(t){let e=0;for(const s of t.ol)-1==s&&e++;return e}
634
- /**
635
- * Return current game state, so you can save it somewhere.
636
- * @returns {MiserState}
637
- */getGameState(){return this.#e}
638
- /**
639
- * Set new game state.
640
- * Call this after you restore a previously saved game.
641
- * @param {MiserState} miserState
642
- */setGameState(t){this.#e=t}isGameInProgress(){return this.#e.cp>0}
643
- /* ##### Start of STATIC DATA ###### */
644
- /* This data is not modified in the original source code. */
645
- /**
646
- * List of actions, such as LOOK, GET, MOVE, etc.
647
- * v$ (verbs) begins at 1 in the original program.
648
- * Setting index 0 to "" here.
649
- * Defined at Line 500 in the original program from 1981.
650
- * STATIC DATA: This array was not modified in the original source code.
651
- * @type {string[]}
652
- */static#z=["","get","take","move","slid","push","open","read","inve","quit","drop","say","pour","fill","unlo","look","go","nort","n","sout","s","east","e","west","w","scor","turn","jump","swim","i","fix"];
653
- /**
654
- * List of objects.
655
- * o$ (objects) begins at 1 in the original program. Setting index 0 to "" here.
656
- *
657
- * STATIC DATA: This array was not modified in the original source code.
658
- * @type {string[]}
659
- */
660
- static#F=["","ripc","mat","pape","buck","swor","key","valv","ladd","slip","rug","book","door","cabi","ritn","vict","orga","para","stai","penn","cros","leaf","bag",">$<",">$<","ring","pain","vaul","pool","xyzz","plug"];
661
- /**
662
- * Error messages.
663
- * Alternates between index 1 and 2 via the em variable.
664
- * em starts at 1 in the program.
665
- * em=3-em changes that to em=2 and
666
- * the next em=3-em changes that em=2 to em=1.
667
- * STATIC DATA: This array was not modified in the original source code.
668
- * @type {string[]}
669
- */
670
- static#L=["","What?","I don't understand that."];
671
- /**
672
- * Rank descriptions.
673
- * STATIC DATA.
674
- * @type {string[]}
675
- */
676
- static#$=["<Beginner Adventurer>","<Amateur Adventurer>","<Journeyman Adventurer>","<Experienced Adventurer>","<Pro Adventurer>","<Master Adventurer>","<Grandmaster Adventurer>"];static#D=["",// om$ begins at 1 in the original program. Setting index 0 to "" here.
677
- "plastic bucket","vicious snake","charmed snake","*golden leaf*","*bulging moneybag*",">$<","*diamond ring*","*rare painting*","sword","mat","rusty cross","penny","piece of paper","parachute with no ripcord","oriental rug","trapdoor marked 'danger'","parachute ripcord","portal in the north wall","pair of *ruby slippers*","brass door key","majestic staircase leading up","majestic staircase leading down","battered book","organ in the corner","open organ in the corner","cabinet on rollers against one wall over","repaired parachute","sign saying 'drop coins for luck'"];static#B=["front porch","Foyer to a large house. Dust is everywhere","Great Hall. Suits of armor line the walls","Breakfast Room. It is bright and cheery","Conservatory. Through a window you see a hedge-maze","Red-Walled Room","Formal Parlor","Green Drawing Room","Trophy Room. Animal heads line the walls","Den","Blue Drawing Room","Library. Empty shelves line the walls","Dining Room","Chinese Room","$","Kitchen. It is bare","Pantry. Dust covers the mahogany shelves","Game Room","Smoking Room. The air is stale in here","Portico. A murky pool glimmers on the south side","Hall Of Mirrors - a good place to reflect","Ballroom. It has a beautiful wood dance floor","Chapel. A tablet says 'Drop a religious item or die!!'","back yard","forest","Pool Area. There is a large swimming pool here","Pump House. There is pool machinery installed here","middle of the Western Hallway","West Bedroom","Front Balcony. There is a large road below","$","Master Bedroom. There's a huge four-poster bed","Rear Balcony. Below you see a Hedge Maze","East Bedroom","Closet","Junction of the West Hallway and the North-South Hallway","Center of the North-South Hallway","Junction of the East Hallway and the North-South Hallway","Middle of the East Hallway","South end of the East Hallway","hedge maze","hedge maze","hedge maze","hedge maze","hedge maze","hedge maze","walk-in Vault","Dungeon. There is light above and to the south","bottom of the Swimming Pool. A ladder leads up and out"];static#W=["Front Porch","Foyer","Great Hall","Breakfast Room","Conservatory","Red-Walled Room","Formal Parlor","Green Drawing Room","Trophy Room","Den","Blue Drawing Room","Library","Dining Room","Chinese Room","$","Kitchen","Pantry","Game Room","Smoking Room","Portico","Hall of Mirrors","Ballroom","Chapel","Back Yard","Forest","Pool Area","Pump House","Middle of West Hallway","West Bedroom","Front Balcony","$","Master Bedroom","Rear Balcony","East Bedroom","Closet","West Hallway Junction","Center of North-South Hallway","East Hallway Junction","Middle of East Hallway","South End East Hallway","hedge maze","hedge maze","hedge maze","hedge maze","hedge maze","hedge maze","Vault","Dungeon","Swimming Pool"];static#A=Object.freeze({DIED:"died",ESCAPED:"escaped",QUIT:"quit"});
678
- /* ##### End of STATIC DATA ###### */
679
- /* ###### Variable Game State Data ###### */
680
- /* Gets deep copied into a new MiserState object for a new game. */
681
- /**
682
- * @type {MiserState}
683
- */
684
- static#n={cp:0,em:1,gt:0,du:!1,pf:!0,fb:!0,bf:!1,fv:!1,vo:!1,po:!1,ch:!1,ps:!1,kc:!1,es:!1,jm:!1,gg:!1,
685
- /**
686
- * Object Pointer array.
687
- * pt starts at index 1 in the original program. I'm keeping that here and just setting index 0 to 0.
688
- * The index numbers align with the o$ objects array: o$(1)=ripcord, so pt%(1)'s value is an index pointer for the ripcord, into ol% and om$.
689
- * A (-1) stored here means the object doesn't have an action for it, such as move, open, etc.
690
- * The original 1981 program will crash when looking up ol%(pt%(object_index)) when pt%(object_index) returns -1.
691
- * In that case, ol%(-1) produces an ILLEGAL QUANTITY error and the program ends.
692
- * Many routines will check that pt%(object_index) isn't equal to a -1 before using it with ol% or om$, but some don't.
693
- * For instance, if you try to DROP the CABINET, ORGAN, STAIRS, or any other object that has a -1 in pt%, the program crashes.
694
- * (On crash, on the PET or in Vice/XPET on PC: type GOTO 14000 and you can resume the game. 14000=start of LOOK routine.)
695
- * DYNAMIC DATA.
696
- * @type {number[]}
697
- */
698
- pt:[0,17,10,13,1,9,20,-1,-1,19,15,23,-1,-1,-1,-1,-1,14,-1,12,11,4,5,-1,-1,7,8,-1,-1,-1,-1],
699
- /**
700
- * Object Location array.
701
- * ol starts at index 1 in the original program. I'm keeping that here and just setting index 0 to 0.
702
- * A (-1) stored here means the player is carrying the object. It will be listed by the I or INVEntory command.
703
- * A (-2) stored here means the object is hidden.
704
- * DYNAMIC DATA.
705
- * @type {number[]}
706
- */
707
- ol:[0,26,4,-2,45,46,-2,48,39,13,0,// Index 10 - Mat location, 0 = rString[0] = front porch
708
- 23,28,31,34,6,-2,-2,-2,-2,-2,// Index 20 - Brass Door Key, -2 = doesn't exist yet. Will change to 0 (front porch) when the mat is moved
709
- 2,27,11,21,-2,5,-2,19],
710
- // This entire array is being saved in MiserState.
711
- // Most of this array is static data, but 3 rooms are modified in the original source code.
712
- // rPercent[5][3] gets modified. (Red-walled room. East becomes 46, to go into vault. )
713
- // rPercent[8][1] gets modified. (Trophy room. North becomes 17, to go to Game Room. )
714
- // rPercent[21][3] gets modified. (Ballroom. East becomes 22, to go to Chapel.)
715
- // I thought someone might want to add additional floors, rooms, or portals that would require
716
- // updating even more of these arrays, so I'm treating them all as dynamic/changing/must-save for now.
717
- rPercent:[[0,1,0,0,0],[0,2,0,0,12],[0,3,1,0,0],[0,0,2,4,16],[0,0,5,7,3],[0,4,6,0,0],[0,5,0,10,0],[0,0,0,8,4],[0,0,9,0,7],[0,8,0,0,10],[0,0,11,9,6],[0,10,0,0,0],[0,0,0,1,13],[0,15,0,12,0],[0,0,0,0,0],[0,23,13,16,0],[0,0,0,3,15],[0,0,8,0,18],[0,21,0,17,19],[0,21,0,18,20],[0,21,21,19,19],[0,0,19,0,20],[0,0,0,0,21],[0,24,15,40,25],[0,24,23,24,24],[0,26,0,23,0],[0,0,25,0,0],[0,35,0,31,28],[0,0,0,27,0],[0,39,0,0,0],[0,0,0,0,0],[0,0,0,38,27],[0,0,36,0,0],[0,34,0,0,38],[0,0,33,0,0],[0,0,27,36,0],[0,32,0,37,35],[0,0,38,0,36],[0,37,39,33,31],[0,38,29,0,0],[0,0,42,0,41],[0,44,42,0,0],[0,41,44,43,0],[0,41,23,0,0],[0,0,42,0,45],[0,0,0,44,0],[0,0,0,0,5],[0,0,40,0,0],[0,0,0,0,0]]}}
718
- /**
719
- * @typedef {Object} MiserState
720
- * @property {number} cp Current Position
721
- * @property {number} em Error Message.
722
- * @property {number} gt Got Treasure.
723
- * @property {boolean} du Door Unlocked.
724
- * @property {boolean} pf Pool Full.
725
- * @property {boolean} fb Fire Burning.
726
- * @property {boolean} bf Bucket Full.
727
- * @property {boolean} fv Found Vault.
728
- * @property {boolean} vo Vault Open.
729
- * @property {boolean} po Portal Open.
730
- * @property {boolean} ch CHarmed snake.
731
- * @property {boolean} ps Peeved Snake.
732
- * @property {boolean} kc Knows Combination.
733
- * @property {boolean} es EScaped.
734
- * @property {boolean} jm Jump Made.
735
- * @property {boolean} gg Got God? [I mean, it is related to the 'drop a religious item or die' and the cross...]
736
- * @property {number[]} ol Object location array.
737
- * @property {number[]} pt Object pointer array.
738
- * @property {Array<number[]>} rPercent Rooms map - N S E W. (new int[49,5])
739
- */
740
- /** @typedef {Object} GameOverReason
741
- * @property {string} DIED
742
- * @property {string} ESCAPED
743
- * @property {string} QUIT
744
- */
745
- /**
746
- * @typedef {Object} MiserResponse
747
- * @property {string} text Text that will be displayed to the player.
748
- * @property {boolean|string} gameOver One of 'died', 'escaped', quit', or false.
749
- * @property {boolean|string} isError Error text.
750
- */
1
+ export default class MiserJSEngine{#t="";#e;#s=null;#r=null;static#i=40;constructor(t=null){this.#e=t||JSON.parse(JSON.stringify(MiserJSEngine.#n))}newGame(){this.#e=JSON.parse(JSON.stringify(MiserJSEngine.#n))}request(t){this.#t="";let e=t;if(!e)return this.#o("No input was provided.",!1,!0);e=e.trim(),e.length>MiserJSEngine.#i&&(e=e.substring(0,MiserJSEngine.#i));let s,r,i=0,n=0,o=[];do{" "!==e.charAt(i)?i++:(i>n&&o.push(e.substring(n,i)),i++,n=i)}while(i<e.length);switch(i>n&&o.push(e.substring(n,i)),o.length){case 1:return s=this.#a(o[0]),s>0?(this.#s=o[0],this.#h(s,0)):this.#u();case 2:return s=this.#a(o[0]),s>0?(this.#s=o[0],r=this.#p(o[1]),r>0?(this.#r=o[1],this.#h(s,r)):this.#u()):this.#u();default:return this.#o("Please type a one or two word command.\n")}}#h(t,e){switch(t){case 1:case 2:return this.#l(e);case 3:case 4:case 5:return this.#c(e);case 6:return this.#m(e);case 7:return this.#S(e);case 8:case 29:return this.#d();case 9:return this.#g();case 10:return this.#f(e);case 11:return this.#T(e);case 12:return this.#y(e);case 13:return this.#w(e);case 14:return this.#b(e);case 15:return this.#x();case 16:return this.#k(e);case 17:case 18:return this.#v();case 19:case 20:return this.#I();case 21:case 22:return this.#E();case 23:case 24:return this.#P();case 25:return this.#M();case 26:return this.#Y(e);case 27:return this.#R();case 28:return this.#J();case 30:return this.#O(e);default:throw new Error(`In Action method: Verb '${this.#s}' was defined in the verbs array, but not implemented yet.`)}}#l(t){if(0==t)return this.#u();if(-1==this.#e.pt[t])return this.#o("I am unable to do that.\n");if(-1==this.#G(t))return this.#o("You're already carrying it.\n");if(this.#G(t)!=this.#e.cp)return this.#N();this.#e.ol[this.#e.pt[t]]=-1,this.#t+="Ok\n";let e=this.#e.pt[t];return e>3&&e<9||19===e?(this.#e.gt+=1,this.#t+="You got a treasure!\n",this.#o(this.#t)):(2===t&&-2===this.#e.ol[20]&&(this.#e.ol[20]=0,this.#t+="You find a door key!\n"),this.#o(this.#t))}#c(t){if(0==t)return this.#u();if(13!=t){if(-1==this.#e.pt[t])return this.#o("That item stays put.\n");if(this.#e.ol[this.#e.pt[t]]!=this.#e.cp&&-1!=this.#e.ol[this.#e.pt[t]])return this.#N()}switch(t){case 2:if(-2==this.#e.ol[20])return this.#e.ol[20]=0,this.#o("You find a door key!\n");break;case 10:if(-2==this.#e.ol[16])return this.#t+="You find a trap door!\n",this.#e.ol[16]=6,this.#x();break;case 13:return 5==this.#e.cp&&0==this.#e.rPercent[5][3]?(this.#e.fv=!0,this.#t+="Behind the cabinet is a vault!\n",this.#x()):this.#o("That item stays put.\n")}return this.#o("Moving it reveals nothing.\n")}#m(t){switch(t){case 0:return this.#u();case 7:return this.#o("Try turning it.\n");case 11:if(this.#e.ol[this.#e.pt[t]]==this.#e.cp||-1==this.#e.ol[this.#e.pt[t]])return this.#o("Scrawled in blood on the inside front cover is the message, \"'Victory' is a prize-winning word\".\n");break;case 12:switch(this.#e.cp){case 0:return this.#e.du?this.#o("It's already open.\n"):this.#o("Sorry, the door is locked.\n");case 6:return this.#t+="You open the door. You lean over to peer in, and you fall in!\n",this.#e.cp=47,this.#x()}return this.#N();case 13:return this.#e.ol[26]==this.#e.cp?this.#o("The cabinet is empty and dusty.\nScribbled in dust on one shelf are the words, 'behind me'.\n"):this.#N();case 16:return 21==this.#e.cp?this.#e.gg?-2!=this.#e.ol[24]?(this.#e.ol[24]=-2,this.#e.ol[25]=21,this.#e.ol[17]=21,this.#e.ol[19]=21,this.#t+="As you open it, several objects suddenly appear!\n",this.#x()):this.#o("It's already open.\n"):this.#o("It's stuck shut.\n"):this.#N();case 22:return this.#e.ol[this.#e.pt[t]]==this.#e.cp||-1==this.#e.ol[this.#e.pt[t]]?this.#o("The bag is knotted securely.\nIt won't open.\n"):this.#N();case 27:return 5==this.#e.cp&&this.#e.fv?this.#e.vo?this.#o("It's already open.\n"):this.#o("I can't, it's locked.\n"):this.#N()}return this.#o("I don't know how to open that.\n")}#S(t){if(0==t)return this.#u();if(-1==this.#e.pt[t])return this.#o("There's nothing written on that.\n");if(!this.#j(t))return this.#N();switch(t){case 3:return this.#e.kc=!0,this.#o("It says, '12-35-6'.\nhmm.. looks like a combination.\n");case 11:return this.#o("The front cover is inscribed in Greek.\n");default:return this.#o("There's nothing written on that.\n")}}#d(){this.#t+="You are carrying the following:\n\n";let t=0;for(let e=1;e<28;e++)if(-1==this.#e.ol[e]){if(t=1,this.#t+=`${MiserJSEngine.#D[e]}\n`,1==e&&this.#e.bf){this.#t+=" The bucket is full of water.\n";continue}14==e&&(this.#t+=" (Better fix it)\n")}return 1==t?this.#o(this.#t):this.#o(this.#t+="Nothing at all.\n")}#g(){return this.#o(this.#t,MiserJSEngine.#A.QUIT)}#f(t){if(-1==this.#e.pt[t])return this.#t+="\n?ILLEGAL QUANTITY ERROR IN 8000\n\n",this.#t+="----------------------------------------",this.#t+="** You have encountered a bug that\n",this.#t+="existed in the original Miser program\n",this.#t+="from 1981. **\n\n",this.#t+="This bug is being reproduced here to\n",this.#t+="preserve the experience a player would\n",this.#t+="have had playing this game in 1981 on\n",this.#t+="one of the Commodore PET computers.\n\n",this.#t+="Except for one thing...the game doesn't\n",this.#t+="just end unexpectedly here as it did\n",this.#t+="back then.\n\n",this.#t+="You get to keep playing as if nothing\n",this.#t+="bad happened!\n",this.#o(this.#t);if(-1!=this.#e.ol[this.#e.pt[t]])return this.#o("You aren't carrying it!\n");switch(this.#e.pt[t]){case 4:case 5:case 7:case 8:case 19:return this.#o("Don't drop *treasures*!\n")}switch(t){case 19:if(19==this.#e.cp)return this.#e.rPercent[21][3]=22,this.#e.ol[12]=-2,this.#o("As the penny sinks below the surface of the pool, a fleeting image of a chapel with dancers outside appears.\n");break;case 20:if(22==this.#e.cp)return this.#e.gg=!0,this.#e.ol[11]=-2,this.#o("Even before it hits the ground, the cross fades away!\n\nThe tablet has disintegrated.\n\nYou hear music from the organ.\n")}return this.#e.ol[this.#e.pt[t]]=this.#e.cp,this.#o("Ok\n")}#T(t){switch(t){case 0:return this.#o("Say what???\n");case 14:return 4==this.#e.cp?this.#e.ch?this.#o("Nothing happens.\n"):(this.#e.ch=!0,this.#e.ol[2]=-2,this.#e.ol[3]=4,this.#o("The snake is charmed by the very utterance of your words.\n")):this.#o("Nothing happens.\n");case 15:return 8==this.#e.cp?this.#e.po?this.#o("Nothing happens.\n"):(this.#e.po=!0,this.#e.rPercent[8][1]=17,this.#e.ol[18]=8,this.#o("A portal has opened in the north wall!!\n")):this.#o("Nothing happens.\n");case 29:case 30:return this.#o("A hollow voice says, 'Wrong adventure'.\n");default:return t<29?this.#o(`Okay, '${this.#r}'.\nNothing happens.\n`):this.#u()}}#y(t){if(4!=t)return this.#o("I wouldn't know how.\n");if(!this.#j(t))return this.#N();if(!this.#e.bf)return this.#o("The bucket is already empty.\n");switch(this.#e.cp){case 10:if(this.#e.fb)return this.#e.fb=!1,this.#e.bf=!1,this.#t+="Congratulations! You have vanquished the flames.\n",this.#x();break;case 19:return this.#o("Ok\n")}return this.#o("The water disappears quickly.\n")}#w(t){if(0==t)return this.#u();if(-1==this.#e.pt[t])return this.#o("That wouldn't hold anything.\n");if(!this.#j(t))return this.#N();if(4==t){if(this.#e.bf)return this.#o("It's already full.\n");switch(this.#e.cp){case 19:case 23:return this.#e.bf=!0,this.#o("Your bucket is now full.\n");case 25:if(this.#e.pf)return this.#o("I'd rather stay away from the mercury.\n")}return this.#o("I don't see any water here.\n")}return this.#o("That wouldn't hold anything.\n")}#b(t){switch(t){case 0:return this.#u();case 12:switch(this.#e.cp){case 0:return this.#e.du?this.#o("It's already unlocked.\n"):-1!=this.#e.ol[20]?this.#o("I need a key.\n"):(this.#e.du=!0,this.#t+="The door easily unlocks and swings open.\n",this.#x());case 6:return-2!=this.#e.ol[16]?this.#o("The trapdoor has no lock.\n"):this.#o("I don't see it here.\n");default:return this.#N()}case 27:return 5==this.#e.cp?this.#e.vo?this.#o("It's already open.\n"):this.#e.fv?this.#e.kc?(this.#e.vo=!0,this.#e.rPercent[5][3]=46,this.#t+="Ok, let's see. 12..35..6..\n<CLICK!> The door swings open.",this.#x()):this.#o("I don't know the combination.\n"):this.#N():this.#N();default:return this.#o("I wouldn't know how to unlock one.\n")}}#x(){let t=this.#e.cp;22==t&&this.#e.gg?this.#t+="\nYou are in the chapel.\n":this.#t+=`\nYou are in the ${MiserJSEngine.#B[t]}.\n`;for(let e=1;e<29;e++)this.#e.ol[e]==t&&(24==e&&this.#e.gg?this.#t+="\nThere is a closed organ playing music in the corner here.\n":this.#t+=`\nThere is a ${MiserJSEngine.#D[e]} here.\n`,1==e&&this.#e.bf&&(this.#t+=" The bucket is full of water.\n"));switch(this.#e.cp){case 0:this.#e.du&&(this.#t+="\nAn open door leads north.\n");break;case 5:this.#e.fv&&(this.#t+="\nThere is a vault in the east wall.\n"),this.#e.vo&&(this.#t+="The vault is open.\n");break;case 10:this.#e.fb?this.#t+="\nThere is a hot fire on the south wall!\nIf I go that way I'll burn to death!\n":this.#t+="\nThere is evidence of a recent fire here.\n";break;case 16:this.#t+="\nA rich, full voice says, 'Ritnew is a charming word'.\n";break;case 23:this.#t+="\nThere is a leaky faucet nearby.\n";break;case 25:this.#e.pf?this.#t+="\nThe pool is full of liquid mercury!\n":(this.#t+="\nThe pool's empty.\n",48==this.#e.ol[7]&&(this.#t+="\nI see something shiny in the pool!\n"));break;case 26:this.#t+="\nThere is a valve on one of the pipes.\n";break;case 48:return this.#o(this.#t)}return this.#t+="\nObvious Exits:\n",this.#e.rPercent[this.#e.cp][1]>0&&(this.#t+="N "),this.#e.rPercent[this.#e.cp][2]>0&&(this.#t+="S "),this.#e.rPercent[this.#e.cp][3]>0&&(this.#t+="E "),this.#e.rPercent[this.#e.cp][4]>0&&(this.#t+="W "),this.#t+="\n",this.#o(this.#t)}#k(t){switch(t){case 8:return 48==this.#e.cp?(this.#e.cp=25,this.#x()):this.#N();case 18:switch(this.#e.cp){case 2:return-1==this.#e.ol[9]?(this.#e.cp=27,this.#t+="The suits of armor try to stop you, but you fight them off with your sword.\n",this.#x()):this.#o("The suits of armor prevent you from going up!\n");case 27:return this.#e.cp=2,this.#x();default:return this.#N()}case 28:return this.#e.pf?this.#o("The pool is full of mercury!\n"):(this.#e.cp=48,this.#x());default:return this.#u()}}#v(){return 0!=this.#e.cp||this.#e.du?0==this.#e.rPercent[this.#e.cp][1]?this.#C():(0==this.#e.cp&&(this.#t+="\nThe door slams shut behind you!\n"),this.#e.cp=this.#e.rPercent[this.#e.cp][1],this.#x()):(this.#t+="The door is locked shut.\n",this.#o(this.#t))}#I(){if(10==this.#e.cp&&this.#e.fb)return this.#t+="You have burnt to a crisp!\n",this.#o(this.#t,MiserJSEngine.#A.DIED);const t=this.#H();return null!=t?t:0==this.#e.rPercent[this.#e.cp][2]?this.#C():(this.#e.cp=this.#e.rPercent[this.#e.cp][2],this.#x())}#E(){const t=this.#H();return null!=t?t:0==this.#e.rPercent[this.#e.cp][3]?this.#C():(this.#e.cp=this.#e.rPercent[this.#e.cp][3],this.#x())}#P(){return 0==this.#e.rPercent[this.#e.cp][4]?this.#C():(this.#e.cp=this.#e.rPercent[this.#e.cp][4],this.#x())}#M(){this.#t+=`Your current score is: ${20*this.#e.gt} points.\n(100 possible)\n`;let t=this.#e.gt;this.#e.es&&t++,this.#t+=`\nYour rank is: ${MiserJSEngine.#$[t]}\n`;let e=MiserJSEngine.#$.length-1-t;return this.#t+=0==e?" (You have escaped with ALL available treasure!)\n":1==e?" (There is only one rank left for you to achieve!)\n":` (There are ${e} more ranks you can achieve.)\n`,this.#o(this.#t)}#Y(t){return 7!=t?this.#o("I don't know how to turn such a thing.\n"):26!=this.#e.cp?this.#N():(this.#e.pf=!this.#e.pf,this.#o("With much effort, you turn the valve 5 times. You hear the sound of liquid\nflowing through the pipes.\n"))}#R(){switch(this.#e.cp){case 27:return this.#t+="You jump...\n",this.#e.jm?(this.#t+="Now you've done it. You ignored\nmy warning, and as a result\nyou have broken your neck!\n\nYou are dead.",this.#o(this.#t,MiserJSEngine.#A.DIED)):(this.#e.jm=!0,this.#e.cp=2,this.#t+="You have landed down-stairs,\nand narrowly escaped serious\ninjury. Please don't try it again.\n",this.#x());case 29:case 32:if(this.#t+="You jump...\n",-1==this.#e.ol[14])return this.#t+="There is no way to open the parachute!\n",this.#t+="You hit the ground.\n",this.#t+="You have broken your neck!\n\n",this.#t+="You are dead.",this.#o(this.#t,MiserJSEngine.#A.DIED);if(-1!=this.#e.ol[27])return this.#t+="You hit the ground.\n",this.#t+="You have broken your neck!\n\n",this.#t+="You are dead.",this.#o(this.#t,MiserJSEngine.#A.DIED);if(this.#t+="You yank the ripcord and the\n'chute comes billowing out.\n",32==this.#e.cp)return this.#e.cp=40,this.#x();if(29==this.#e.cp)return this.#t+="You land safely.\n\nCongratulations on escaping!\n",this.#e.es=!0,this.#o(this.#t,MiserJSEngine.#A.ESCAPED);default:return this.#t+="There's nowhere to jump.",this.#o(this.#t)}}#J(){switch(this.#e.cp){case 19:return this.#o("The water is only a few inches deep.\n");case 25:return this.#e.pf?this.#o("In mercury? No way!\n"):this.#o("The pool is empty.\n");default:return this.#o("There's nothing here to swim in!\n")}}#O(t){switch(t){case 0:return this.#u();case 7:return this.#o("I ain't no plumber.\n");case 17:return this.#j(t)?-2==this.#e.ol[14]?this.#o("It's already fixed.\n"):-1!=this.#e.ol[17]?this.#o("I need a ripcord.\n"):(this.#e.ol[27]=this.#e.ol[14],this.#e.ol[14]=-2,this.#e.pt[17]=27,this.#e.ol[17]=0,this.#o("I'm no expert, but I think it'll work.\n")):this.#N();default:return this.#o("I wouldn't know how.\n")}}#H(){return 4!=this.#e.cp||this.#e.ch?null:this.#e.ps?this.#o("The snake bites you!\nYou are dead.\n",MiserJSEngine.#A.DIED):(this.#e.ps=!0,this.#o("The snake is about to attack!\n"))}showFinalOutcome(){this.#t=`\nYou accumulated ${this.#e.gt} treasures, \n for a score of ${20*this.#e.gt} points.\n(100 Possible)\n`;let t=this.#e.gt;return this.#e.es?t++:this.#t+="\nHowever, you did not escape.\n",this.#t+=`\nThis puts you in a class of:\n${MiserJSEngine.#$[t]}\n`,6!=t&&(this.#t+="\nBetter luck next time!\n"),this.#o(this.#t,!0)}#o(t,e=!1,s=!1){return{text:t,gameOver:e,isError:s}}#a(t){(t=t.toLowerCase()).length>4&&(t=t.substring(0,4));for(let e=1;e<MiserJSEngine.#z.length;e++)if(t===MiserJSEngine.#z[e])return e;return 0}#p(t){(t=t.toLowerCase()).length>4&&(t=t.substring(0,4));for(let e=1;e<MiserJSEngine.#F.length;e++)if(t===MiserJSEngine.#F[e])return e;return 0}#u(){return this.#t+=`${MiserJSEngine.#L[this.#e.em]}\n`,this.#e.em=3-this.#e.em,this.#o(this.#t)}#N(){return this.#t+="I don't see it here.\n",this.#o(this.#t)}#C(){return this.#t+="It's impossible to go that way.\n",this.#o(this.#t)}#j(t){return this.#G(t)==this.#e.cp||-1==this.#G(t)}#G(t){return this.#e.ol[this.#e.pt[t]]}getCurrentPosition(){return this.#e.cp}getCurrentPositionShortName(t){return MiserJSEngine.#W[t]}getInventoryItemCount(t){let e=0;for(const s of t.ol)-1==s&&e++;return e}getGameState(){return this.#e}setGameState(t){this.#e=t}isGameInProgress(){return this.#e.cp>0}static#z=["","get","take","move","slid","push","open","read","inve","quit","drop","say","pour","fill","unlo","look","go","nort","n","sout","s","east","e","west","w","scor","turn","jump","swim","i","fix"];static#F=["","ripc","mat","pape","buck","swor","key","valv","ladd","slip","rug","book","door","cabi","ritn","vict","orga","para","stai","penn","cros","leaf","bag",">$<",">$<","ring","pain","vaul","pool","xyzz","plug"];static#L=["","What?","I don't understand that."];static#$=["<Beginner Adventurer>","<Amateur Adventurer>","<Journeyman Adventurer>","<Experienced Adventurer>","<Pro Adventurer>","<Master Adventurer>","<Grandmaster Adventurer>"];static#D=["","plastic bucket","vicious snake","charmed snake","*golden leaf*","*bulging moneybag*",">$<","*diamond ring*","*rare painting*","sword","mat","rusty cross","penny","piece of paper","parachute with no ripcord","oriental rug","trapdoor marked 'danger'","parachute ripcord","portal in the north wall","pair of *ruby slippers*","brass door key","majestic staircase leading up","majestic staircase leading down","battered book","organ in the corner","open organ in the corner","cabinet on rollers against one wall over","repaired parachute","sign saying 'drop coins for luck'"];static#B=["front porch","Foyer to a large house. Dust is everywhere","Great Hall. Suits of armor line the walls","Breakfast Room. It is bright and cheery","Conservatory. Through a window you see a hedge-maze","Red-Walled Room","Formal Parlor","Green Drawing Room","Trophy Room. Animal heads line the walls","Den","Blue Drawing Room","Library. Empty shelves line the walls","Dining Room","Chinese Room","$","Kitchen. It is bare","Pantry. Dust covers the mahogany shelves","Game Room","Smoking Room. The air is stale in here","Portico. A murky pool glimmers on the south side","Hall Of Mirrors - a good place to reflect","Ballroom. It has a beautiful wood dance floor","Chapel. A tablet says 'Drop a religious item or die!!'","back yard","forest","Pool Area. There is a large swimming pool here","Pump House. There is pool machinery installed here","middle of the Western Hallway","West Bedroom","Front Balcony. There is a large road below","$","Master Bedroom. There's a huge four-poster bed","Rear Balcony. Below you see a Hedge Maze","East Bedroom","Closet","Junction of the West Hallway and the North-South Hallway","Center of the North-South Hallway","Junction of the East Hallway and the North-South Hallway","Middle of the East Hallway","South end of the East Hallway","hedge maze","hedge maze","hedge maze","hedge maze","hedge maze","hedge maze","walk-in Vault","Dungeon. There is light above and to the south","bottom of the Swimming Pool. A ladder leads up and out"];static#W=["Front Porch","Foyer","Great Hall","Breakfast Room","Conservatory","Red-Walled Room","Formal Parlor","Green Drawing Room","Trophy Room","Den","Blue Drawing Room","Library","Dining Room","Chinese Room","$","Kitchen","Pantry","Game Room","Smoking Room","Portico","Hall of Mirrors","Ballroom","Chapel","Back Yard","Forest","Pool Area","Pump House","Middle of West Hallway","West Bedroom","Front Balcony","$","Master Bedroom","Rear Balcony","East Bedroom","Closet","West Hallway Junction","Center of North-South Hallway","East Hallway Junction","Middle of East Hallway","South End East Hallway","hedge maze","hedge maze","hedge maze","hedge maze","hedge maze","hedge maze","Vault","Dungeon","Swimming Pool"];static#A=Object.freeze({DIED:"died",ESCAPED:"escaped",QUIT:"quit"});static#n={cp:0,em:1,gt:0,du:!1,pf:!0,fb:!0,bf:!1,fv:!1,vo:!1,po:!1,ch:!1,ps:!1,kc:!1,es:!1,jm:!1,gg:!1,pt:[0,17,10,13,1,9,20,-1,-1,19,15,23,-1,-1,-1,-1,-1,14,-1,12,11,4,5,-1,-1,7,8,-1,-1,-1,-1],ol:[0,26,4,-2,45,46,-2,48,39,13,0,23,28,31,34,6,-2,-2,-2,-2,-2,2,27,11,21,-2,5,-2,19],rPercent:[[0,1,0,0,0],[0,2,0,0,12],[0,3,1,0,0],[0,0,2,4,16],[0,0,5,7,3],[0,4,6,0,0],[0,5,0,10,0],[0,0,0,8,4],[0,0,9,0,7],[0,8,0,0,10],[0,0,11,9,6],[0,10,0,0,0],[0,0,0,1,13],[0,15,0,12,0],[0,0,0,0,0],[0,23,13,16,0],[0,0,0,3,15],[0,0,8,0,18],[0,21,0,17,19],[0,21,0,18,20],[0,21,21,19,19],[0,0,19,0,20],[0,0,0,0,21],[0,24,15,40,25],[0,24,23,24,24],[0,26,0,23,0],[0,0,25,0,0],[0,35,0,31,28],[0,0,0,27,0],[0,39,0,0,0],[0,0,0,0,0],[0,0,0,38,27],[0,0,36,0,0],[0,34,0,0,38],[0,0,33,0,0],[0,0,27,36,0],[0,32,0,37,35],[0,0,38,0,36],[0,37,39,33,31],[0,38,29,0,0],[0,0,42,0,41],[0,44,42,0,0],[0,41,44,43,0],[0,41,23,0,0],[0,0,42,0,45],[0,0,0,44,0],[0,0,0,0,5],[0,0,40,0,0],[0,0,0,0,0]]}}