VENTURESPEAK ZX Computing, October, November & December 1986 Part 1: October 1986 Alan Davis, recently returned from the Realms of Interaction, sets out once more on a quest to produce a superior command analysis system. It's often said that the quality of an adventure game is predominantly determined by the originality and imaginative scope of its plot, and really it would be hard to deny this. Virtuosity of programming, text compression, complex language analysis, and similar sophistications have been loudly trumpeted by many a software company, and equally loudly hailed by many a reviewer; yet these count for nothing if the heart of the game - its plot and atmosphere - is dull and uninspired. All this is true, but of course there are limits It's equally possible for an adventure to be wonderfully imaginative in concept, but rendered almost unplayable by inordinately long response times, shabby on-screen presentation, or sheer "unfriendliness". It's not difficult to find examples of this second category even among recent commercial adventures: the grotesque "Lord of the Rings" comes most readily to mind, with its excruciatingly slow responses coupled to a keyboard input routine whose sluggishness makes accurate entry of commands a sore test of patience. Or how about those hordes of Quilled adventures, essentially rapid in response, but so often suffering from stereotyped presentation, unfriendly vocabulary and the rigid restriction of the standard "verb/noun" input requirement. Faced with this situation, the BASIC programmer with a yen to write adventures may well be put off for good. Even if he opts for the simple "verb/noun" input format, the need for a large, friendly vocabulary necessarily implies long delays in response whenever the program decodes the player's input and scans the vocabulary lists. Even then, if this limitation is accepted, there are many occasions when the "verb/noun" type of command analyser is inadequate - for the fact remains that you just can't write an interactive "Hobbit"-style adventure without some capability for dealing with more complex commands. Instant response The obvious solution to the problem is to resort to machine code programming in order to deal with the command analysis aspects of adventure writing, since this has many advantages. First, program responses can be virtually instant, regardless of size of vocabulary. Second, complex and multiple commands (eg. PUT THE JEWEL IN THE CHEST AND LEAVE THE ROOM) can be dealt with, again at no cost in response time. And finally, extra facilities may be incorporated into the command analyser to permit, say, detailed editing facilities to improve "friendliness", or the option of "real time" operation in the completed adventure if required. Such a solution in turn raises its own difficulties - not least that of interfacing the machine code routines with the rest of a predominantly BASIC program. We must also face the problem of (preferably) writing code which offers enough flexibility for us to use it for any adventure we may care to write. This, in a nutshell, is what the Venturespeak command analysis system has been designed for. Even if you have no understanding of machine code programming, you can use Venturespeak yourself to write sophisticated adventures which retain the flexibility and ease of BASIC, yet which are largely free from its speed limitations. Venturespeak essentially consists of a single "package" which can be conveniently divided into 3 sections as follows: 1) A keyboard input routine which accepts typed commands up to 63 characters long, complete with single key- press facilities for deleting / editing commands, and a real- time option. 2) A Parser routine which decodes complex commands containing up to three verbs and four nouns, such as "ASK SAM TO PUT THE COIN IN THE PURSE", as well as permitting several commands to be strung together in sequence using commas, full stops, or "AND". 3) The menu-driven Venturespeak Editor which offers comprehensive and easy-to-use facilities for adding, deleting, or changing vocabulary. It is capable of generating a single block of code which incorporates both keyboard and parsing routines together with the vocabulary needed for any particular adventure. Assembly points This month we'll be dealing with the input routine, and the other aspects of Venturespeak will be tackled in succeeding articles, but before we begin I'd like to make a strong recommendation that you equip yourself with a decent assembler program (if you don't have one already). You can poke in the machine code routines from BASIC of course - and I'll provide decimal dumps for those who wish to do so - but we'll be dealing with over 1K of machine code altogether, and the scope for errors when typing in the long lists of numbers is considerable, even with checksums. An assembler is well worth the investment - I use Hisoft's "DEVPAC" and find it excellent. Now that the hors-d'oeuvres are out of the way, we can get on with this month's main course. Before you start, type CLEAR 59999 to lower RAMTOP so that our code can be stored safely at address 60000 and above. Incidentally, I've chosen this address so that the Venturespeak routines will be compatible with those from the previous "Realms of Interaction" series, should you wish to combine them. (The code is not relocatable). Listing 1 gives the assembly language program for the keyboard input routine. The code is organised at address 60000, and is 391 bytes long. For those assemblerless individuals, Listing 2 provides the (decimal) bytes you'll need in conjunction with the BASIC loader given in Listing 3. Type in Listing 3, and then RUN. Just follow the prompts the program gives you, entering the individual bytes from Listing 2 one at a time, including the checksum after every fifth byte. If a mistake is found, the program will ask you to enter the whole of the offending line of 5 bytes again. The screen display which builds up should be identical to the layout of Listing 2, and this should help you to check everything as you go along. Once the whole thing has been entered without errors, the program will save your code to tape as "KEYBOARD" CODE 60000,391. What, then, does this routine do when you call it with LET m=USR 60000? Well, basically it prints a small "prompt" symbol at the bottom of the screen and waits for you to start typing a command. Each character you type is stored as a single byte above RAMTOP, the bytes being stored sequentially from address 60318 (START) onwards up to a maximum of 63 characters. When you finally press ENTER, the routine does an immediate return to BASIC, with the total length of your typed command held at address 60315 (COUNT). All is then ready for the parser (which of course we don't yet have) to take over and analyse your command. However, this is by no means all that the routine can do for you. The following facilities are available: 1) CAPS SHIFT/0 acts just like the usual Spectrum "DELETE" key, erasing the last character typed. 2) CAPS SHIFT/1 deletes everything you've typed so far. 3) CAPS SHIFT/2 enables you to recall the whole of the last command you entered, either so that you can enter it again unchanged, or for editing using the "DELETE" key. Zero time The routine also has a built-in real time facility which operates as follows: if nothing has been typed, then after about 10 seconds a return to BASIC occurs with the address 60383 (TIME) holding the value zero. If on the other hand a return to BASIC has occurred due to the player entering a command, the address 60383 holds the value 1. This means that on exit from the routine we can test PEEK 60383, and execute appropriate subroutines (say for independent action of characters) if that memory location contains zero. If on the other hand it contains the value 1, we know that a command analysis must be carried out first. If you don't want your adventure to run in real time, this feature can be suppressed by amending the code with the following POKEs: POKE 60033,0: POKE 60034,0: POKE 60035,0 (You might like to save two versions of the code - one with real time, and one without). Alternatively, if you want to keep the real time feature but slow down the pace of things, try poking different values into 60032. POKE 60032,4 will give exits to BASIC every 20 seconds or so, for instance. Still, trying all these things for yourself is much more fun than reading about them, and Listing 4 will enable you to check the routine out. First CLEAR 59999, load in the bytes, type in the lines of BASIC from Listing 4 [file: keytest], and then RUN. When you see the prompt at screen bottom, type in any old command (note that CAPS LOCK is set automatically) and press ENTER. If all is well the lower screen will clear, and your command will be exactly reproduced in the upper screen (confirming that it has been stored correctly in memory). Try pressing CAPS SHIFT/2 to check that the "recall last command" facility is working correctly, and in addition check the delete functions. It's clear, I hope, what the benefits of these facilities are: suppose your last command was "ASK SAM TO ATTACK THE ORC", and that on your next turn you'd like Sam to beat the daylights out of a troll. All you need do is recall your previous command using CS/2, delete the word "ORC", and add the word "TROLL" before pressing ENTER. All OK? Fine. Make yourself a well-earned cup of coffee, for you now have about one third of the complete Venturespeak system safely stashed away on tape. See you next month, with the second instalment... Assembler Listing for KEYBOARD ------------------------------ 60000 3E08 KEYBD LD A,008 60002 326A5C LD (23658),A 60005 CDE2EA CALL CLEAR 60008 CD2EEB CALL PROMPT 60011 AF RESET XOR A 60012 32785C LD (23672),A 60015 32795C LD (23673),A 60018 327A5C LD (23674),A 60021 329BEB LD (COUNT),A 60024 AF WAIT XOR A 60025 32085C LD (23560),A 60028 3A795C WAIT2 LD A,(23673) 60031 FE02 CP 002 60033 D232EB JP NC,TIMEUP 60036 3A085C CONT LD A,(23560) 60039 FE00 CP 000 60041 CA7CEA JP Z,WAIT2 60044 FE06 CP 006 60046 CA7BEB JP Z,REPEAT 60049 FE0D CP 013 60051 CCFBEA CALL Z,CHECKN 60054 FE00 CP 000 60056 CA78EA JP Z,WAIT 60059 FE0C CP 012 60061 CCFBEA CALL Z,CHECKN 60064 FE00 CP 000 60066 CA78EA JP Z,WAIT 60069 FE7B CP 123 60071 D278EA JP NC,WAIT 60074 329DEB LD (CHR),A 60077 FE0D CP 013 60079 CA0AEB JP Z,ENTER 60082 FE0C CP 012 60084 CA55EB JP Z,DELETE 60087 3A9DEB LD A,(CHR) 60090 FE07 CP 007 60092 CA19EB JP Z,WIPE 60095 FE20 CP 032 60097 DA78EA JP C,WAIT 60100 3A9BEB LD A,(COUNT) 60103 FE3F CP 063 60105 CA78EA JP Z,WAIT 60108 219EEB LD HL,START 60111 1600 LD D,000 60113 5F LD E,A 60114 3A9DEB LD A,(CHR) 60117 19 ADD HL,DE 60118 77 LD (HL),A 60119 13 INC DE 60120 CD3EEB CALL CURSOR 60123 7B LD A,E 60124 329BEB LD (COUNT),A 60127 C378EA JP WAIT 60130 AF CLEAR XOR A 60131 CD0116 CALL #1601 60134 CD26EB CALL SETPR 60137 0640 LD B,064 60139 3E20 SCAN LD A,032 60141 C5 PUSH BC 60142 D7 RST #10 60143 C1 POP BC 60144 05 DEC B 60145 78 LD A,B 60146 FE00 CP 000 60148 C2EBEA JP NZ,SCAN 60151 CD26EB CALL SETPR 60154 C9 RET 60155 329DEB CHECKN LD (CHR),A 60158 3A9BEB LD A,(COUNT) 60161 FE00 CP 000 60163 CA09EB JP Z,RESETA 60166 3A9DEB LD A,(CHR) 60169 C9 RESETA RET 60170 CDE2EA ENTER CALL CLEAR 60173 3E01 LD A,001 60175 32DFEB LD (TIME),A 60178 3A9BEB LD A,(COUNT) 60181 329CEB LD (CT2),A 60184 C9 RET 60185 AF WIPE XOR A 60186 329BEB LD (COUNT),A 60189 CDE2EA CALL CLEAR 60192 CD2EEB CALL PROMPT 60195 C378EA JP WAIT 60198 3E16 SETPR LD A,022 60200 D7 RST #10 60201 AF XOR A 60202 D7 RST #10 60203 AF XOR A 60204 D7 RST #10 60205 C9 RET 60206 3E3E PROMPT LD A,062 60208 D7 RST #10 60209 C9 RET 60210 3A9BEB TIMEUP LD A,(COUNT) 60213 FE00 CP 000 60215 C284EA JP NZ,CONT 60218 CDE0EB CALL QUIT 60221 C9 RET 60222 D5 CURSOR PUSH DE 60223 3E11 LD A,017 60225 D7 RST #10 60226 3E06 LD A,006 60228 D7 RST #10 60229 3A9DEB LD A,(CHR) 60232 D7 RST #10 60233 D1 POP DE 60234 C9 RET 60235 3E11 SPACE LD A,017 60237 D7 RST #10 60238 3E07 LD A,007 60240 D7 RST #10 60241 3E20 LD A,032 60243 D7 RST #10 60244 C9 RET 60245 3A9BEB DELETE LD A,(COUNT) 60248 FE1F CP 031 60250 CA70EB JP Z,TLINE 60253 3E08 LD A,008 60255 D7 RST #10 60256 CD4BEB HERE CALL SPACE 60259 3E08 LD A,008 60261 D7 RST #10 60262 3A9BEB LD A,(COUNT) 60265 3D DEC A 60266 329BEB LD (COUNT),A 60269 C378EA JP WAIT 60272 3E16 TLINE LD A,022 60274 D7 RST #10 60275 AF XOR A 60276 D7 RST #10 60277 3E1F LD A,031 60279 D7 RST #10 60280 C360EB JP HERE 60283 3A9BEB REPEAT LD A,(COUNT) 60286 FE00 CP 000 60288 C278EA JP NZ,WAIT 60291 CDE2EA CALL CLEAR 60294 CD2EEB CALL PROMPT 60297 119EEB LD DE,START 60300 3A9CEB LD A,(CT2) 60303 329BEB LD (COUNT),A 60306 0600 LD B,000 60308 4F LD C,A 60309 CD3C20 CALL #203C 60312 C378EA JP WAIT 60315 00 COUNT DEFB 0 60316 00 CT2 DEFB 0 60317 00 CHR DEFB 0 60318 .. START DEFS 65 60383 00 TIME DEFB 0 60384 32DFEB QUIT LD (TIME),A 60387 CDE2EA CALL CLEAR 60390 C9 RET - - - - - Part 2: November 1986 Part 2: Alan Davis' adventure series looks at command analysis. This month brings you the second part of the VENTURESPEAK machine code program, and we'll be taking a look at the gentle art of "parsing the input" - or "command analysis", if you prefer. You're going to need the machine code keyboard routine that you saved last month, as we need to patch this month's program onto it to produce a single block of code. Listing 1 is the assembly language program for the "parser" - this is the program which will scan through a typed input, checking the words against a set of vocabulary data. As you can see it's a fairly lengthy routine so an assembler will make error- free entry considerably easier - but if you don't have an assembler all is not lost, since I've given a decimal dump of the code in Listing 2. All you need to do is type in Listing 3, RUN it, and then enter the numbers from Listing 2 in order, including the checksums after every fifth byte. Take things slowly, checking the screen display against the listing as you go along, and all should be well. When you've finished, the program will save the code to tape for you as "PARSER" CODE 60400,750. Parsing So far, so good, We now need to weld the keyboard and parser sections together as follows: 1) Reset the Spectrum using RANDOMIZE USR 0. 2) Enter CLEAR 59999 as a direct command. 3) Load in the keyboard code you saved last month (LOAD "KEYBOARD" CODE). 4) Load in this month's parser code (LOAD "PARSER" CODE). 5) Now save the whole lot as a single block with SAVE "V-SPEAK" CODE 60000,1150 - and keep it safe somewhere until next month. In addition to this single code block, it's probably wise to keep the two separate parts as well for the present, to facilitate checking in case you discover errors later. Of course, you'll be wanting to know just what this new routine does, and to test it out. We'll certainly be looking at how it works in this article, but as for testing it - well, I'm going to ask you to be patient and wait until next month. You see, the problem is that our parser is actually fairly useless at present because it doesn't yet possess a vocabulary! (Rather like a chap who, though highly intelligent, has no background knowledge to draw upon ...) To put vocabulary into the parser we'll need the VENTURESPEAK EDITOR - and that's our task for next month. --------------------------------------------- Figure 1. Vocabulary list, with each verb, noun and object individually numbered. VERBS D 10 SHI 5 DOW 10 ARM 6 SAY 1 DES 10 BOO 7 TEL 1 GET 11 CHE 8 ASK 1 TAK 11 BOX 9 TAL 1 PIC 11 DOO 10 L 2 DRO 12 LOO 2 PUT 12 N 3 EXA 13 PEOPLE NOR 3 REA 13 S 4 GIV 14 SAM 1 SOU 4 OFF 14 FRE 2 E 5 FIG 15 PET 3 EAS 5 KIL 15 JOH 4 W 6 ATT 15 JAC 5 WES 6 HIT 15 JOE 6 IN 7 ENT 7 OUT 8 OBJECTS LEA 8 U 9 SWO 1 UP 9 DAG 2 CLI 9 SPE 3 ASC 9 KNI 4 --------------------------------------------- For the present though, I'm going to explain the basic principles underlying the parsing system so that when the time comes for you to use it you'll be familiar with all the main features, We can't discuss this in a vacuum, so in Figure 1 I've given a very elementary vocabulary of words which you might expect to find in a typical adventure (printed out from the EDITOR program, in fact). Figure 1 itself raises a few points which need to be mentioned before we go any further: 1) Only the first three letters of any word are significant. This can occasionally give rise to some confusion because the parser can't distinguish between words such as "RAVEN" or "RAVINE", although in practice I've never found this to be a serious problem, myself. (My game "RUNESTONE" uses only three-letter parsing). 2) Vocabulary is divided into three distinct types: VERBS, OBJECTS and PEOPLE. This has advantages over the usual simpler sub-division into VERBS and NOUNS because it helps the error-trapping process when you write your adventure, as you'll see later. 3) Each item of vocabulary is assigned a number between 1 and 254 inclusive. Synonyms are catered for by assigning the same number to different words, so that the verbs SAY, TELL, ASK, and TALK, for example, are all assigned the number 1. This is a good place to make an IMPORTANT NOTE: generally you can assign any number you like to any verb you like, BUT IT IS ESSENTIAL THAT ALL VERBS IMPLYING SPEECH BE ASSIGNED THE NUMBER 1, if the parser routine is to work correctly. Input In practice what happens will be something like this. Your BASIC adventure program will invite input from the player by invoking the keyboard routine: LET M=USR 60000. The player then types in his command, presses ENTER when he's finished, and the routine returns to BASIC having stored the player's input as a string of bytes in the correct addresses above RAMTOP. We now want the parser to scan this stored command and decode it, so the next step is to call the parser routine with LET M=USR 60400. The parser now scans through the player's command, character by character, checking the words it finds against those in its vocabulary, ignoring any words it fails to recognise and skipping on to the next. It eventually stops, either when it reaches the end, or when it reaches a comma, full stop, or the word "AND" (these last items signifying that the player has typed in several commands at one go) and returns to BASIC. The parser having done its job (almost instantaneously of course), we need to extract the fruits of its labours by PEEKing certain addresses. It makes for an easier life if the results of these PEEKs are assigned immediately to BASIC variables with suitably memory-jogging names, like this: LET TELL=PEEK 61124 LET PERS=PEEK 61125 LET VB1=PEEK 61126 LET VB2=PEEK 61127 LET FK1=PEEK 61129 LET OB1=PEEK 61131 LET OB2=PEEK 61132 LET MORE=PEEK 61123 What this all amounts to is that you can extract from any one command the code numbers for up to three verbs, two objects, and two people - so that your BASIC program can then perform the necessary condition tests appropriate to your adventure. A few examples should make things clear. --------------------------------------------- Figure 2. Examples of command analysis EXAMINE THE DAGGER PUT THE BOOK IN THE CHEST A C COMMAND ANALYSIS COMMAND ANALYSIS TELL: 0 TELL: 0 PERS: 0 PERS: 0 VB1 : 13 VB1 : 12 VB2 : 0 VB2 : 7 FK1 : 0 FK1 : 0 OB1 : 2 OB1 : 7 OB2 : 0 OB2 : 8 SAY TO FRED "EXAMINE THE DAGGER"ASK PETER TO GIVE THE KNIFE TO J OHN B D COMMAND ANALYSIS COMMAND ANALYSIS TELL: 1 TELL: 1 PERS: 2 PERS: 3 VB1 : 13 VB1 : 14 VB2 : 0 VB2 : 0 FK1 : 0 FK1 : 4 OB1 : 2 OB1 : 4 OB2 : 0 OB2 : 0 --------------------------------------------- Examining Figure 2 gives examples of the analysis of a range of commands involving the basic vocabulary of Figure 1. (You might like to know that these figures are all screen dumps from the VENTURESPEAK EDITOR in "test" mode). Let's start with the first and simplest command - 2(a): "EXAMINE THE DAGGER". Only two words are really relevant here - the verb "EXAMINE", and the noun (in this case an object) "DAGGER". You'll see in the example that the parser has assigned the value 13 to VB1, 2 to OB1, and zero to everything else. Now look back at the vocabulary list in Figure 1, where you'll find that verb number 13 is "EXA" (for "examine"), and that object number 2 is "DAG" (for "dagger"). Makes sense? OK, then let's try something a little more complex. The second example, Figure 2(b), shows the analysis of the command "SAY TO FRED "EXAMINE THE DAGGER"" and we find VB1 and OB1 assigned just the same values as before (as we'd expect) but look: this time the variable TELL takes the value 1I (signifying speech) and the variable PERS is given the value 2 (the number for Fred) corresponding to the person being addressed. Note that TELL is really only a flag which takes the value 1 or 0 according to whether speech is signified or not; it's the variable PERS which conveys the information about whom the player is trying to speak to. So why do we need the extra variables on VB2 and OB2? Well, sometimes commands may involve two objects - and there may be instances when you want to distinguish between two slightly different types of action, such as putting objects ON or IN other subjects. Figure 2(c) shows the analysis of the command "PUT THE BOOK IN THE CHEST". See how the two verbs "PUT" (12) and "IN" (7) are picked up here, together with the two objects in order: "BOOK" (7) and "CHEST" (8). The final example, 2(d) is of the type where speech to one person involves some kind of action with yet another person. And so the command "ASK PETER TO GIVE THE KNIFE TO JOHN" sets the speech flag (TELL) and sets PERS to 3 ("PETER"). As we'd expect, VB1 is 14 ("GIVE") and OB1 is 4 ("KNIFE") but in addition to this, FK1 has picked up the other person involved in the transaction ("JOHN"=4). We see here, by the way, how the separation of nouns into PEOPLE and OBJECTS eases the error checking process needed in every adventure. If a command is found to contain a verb such as "GIVE" or "FIGHT" (where some other person must of necessity be specified), it can be rejected as an unacceptable entry without further enquiry if FK1 is zero. Example 2(d) also illustrates the essential friendliness of VENTURESPEAK, particularly where speech commands are concerned. It matters not a jot what style of entry is used by the player - SAY TO PETER "GIVE JOHN THE KNIFE" for example, will be decoded with complete success by the parser. I haven't mentioned how the system copes with multiple commands yet. Suppose the command GET THE SWORD AND GO NORTH has been entered. The parser will analyse the first part (GET THE SWORD) and then return to BASIC, but it will remember how far along the command it has scanned. If we LET MORE=PEEK 61123 (see above), the variable MORE will be assigned the value 1 - telling us in effect that another part of the command remains to be analysed. (For single commands MORE will be zero). This means we can arrange for the first command to be processed by an appropriate BASIC subroutine, and then test the variable MORE to see whether another analysis should follow, or whether a return to keyboard control is called for. However, the re-entry point to the parser routine is different should a continuation of analysis be necessary - on such occasions the parser is called with LET M=USR 60426. (This is because the usual entry at USR 60000 involves a tidying-up process which would cause the parser to "forget" the position it had reached on its previous scan). A typical programming "flow chart", therefore, would be something like this: (1) CALL KEYBOARD ROUTINE (USR 60000) (2) CALL PARSER ROUTINE (USR 60400) (3) PROCESS COMMAND IN BASIC (4) IF MORE THEN LET M=USR 60426: GOTO (3) (5) GO BACK TO (1) I hope that by now you've begun to get some idea, in principle, of what VENTURESPEAK can do for your adventure writing. If you're burning for some "hands-on" experience, don't worry. You'll be able to try out all these examples (and as many others as you wish) yourself with the help of the EDITOR program next time, when I'll also be offering more detailed explanations of how to incorporate the system into your own programs, Till then ... Assembler Listing for PARSER ---------------------------- 60400 219EEB INIT LD HL,START 60403 22BEEE LD (NCOM),HL 60406 22C0EE LD (PTR),HL 60409 3A9BEB LD A,(COUNT) 60412 5F LD E,A 60413 1600 LD D,000 60415 19 ADD HL,DE 60416 3EFF LD A,255 60418 77 LD (HL),A 60419 AF XOR A 60420 32C4EE LD (TELL),A 60423 32C5EE LD (PERS),A 60426 AF RESTRT XOR A 60427 32C6EE LD (VB1),A 60430 32C7EE LD (VB2),A 60433 32C8EE LD (VB3),A 60436 32C9EE LD (FK1),A 60439 32CAEE LD (FK2),A 60442 32CBEE LD (OB1),A 60445 32CCEE LD (OB2),A 60448 CD74EC CALL COMMA 60451 2AC0EE LOOP8 LD HL,(PTR) 60454 AF XOR A 60455 32DAEE LD (EXTR),A 60458 CD9AEC CALL FILTER 60461 3ADAEE LD A,(EXTR) 60464 FE01 CP 001 60466 CA4FEC JP Z,SORT 60469 2AC0EE LD HL,(PTR) 60472 7E LD A,(HL) 60473 FEFF CP 255 60475 CA4FEC JP Z,SORT 60478 FE2C CP 044 60480 CA4BEC JP Z,END 60483 FE2E CP 046 60485 CA4BEC JP Z,END 60488 C323EC JP LOOP8 60491 23 END INC HL 60492 22C0EE LD (PTR),HL 60495 3AC6EE SORT LD A,(VB1) 60498 FE01 CP 001 60500 CC58EC CALL Z,SWAP 60503 C9 RET 60504 32C4EE SWAP LD (TELL),A 60507 3AC7EE LD A,(VB2) 60510 32C6EE LD (VB1),A 60513 3AC8EE LD A,(VB3) 60516 32C7EE LD (VB2),A 60519 3AC9EE LD A,(FK1) 60522 32C5EE LD (PERS),A 60525 3ACAEE LD A,(FK2) 60528 32C9EE LD (FK1),A 60531 C9 RET 60532 2ABEEE COMMA LD HL,(NCOM) 60535 7E LOOP LD A,(HL) 60536 FEFF CP 255 60538 CA95EC JP Z,ENDCOM 60541 FE2C CP 044 60543 CA8BEC JP Z,SETCOM 60546 FE2E CP 046 60548 CA8BEC JP Z,SETCOM 60551 23 INC HL 60552 C377EC JP LOOP 60555 23 SETCOM INC HL 60556 22BEEE LD (NCOM),HL 60559 3E01 LD A,001 60561 32C3EE LD (MORE),A 60564 C9 RET 60565 AF ENDCOM XOR A 60566 32C3EE LD (MORE),A 60569 C9 RET 60570 7E FILTER LD A,(HL) 60571 FE22 CP 034 60573 CCF6EC CALL Z,SKIPSP 60576 FE20 CP 032 60578 CCF6EC CALL Z,SKIPSP 60581 FE3A CP 058 60583 CCF6EC CALL Z,SKIPSP 60586 FEFF CP 255 60588 C8 RET Z 60589 FE2C CP 044 60591 C8 RET Z 60592 FE2E CP 046 60594 C8 RET Z 60595 23 INC HL 60596 7E LD A,(HL) 60597 FE20 CP 032 60599 CA0BED JP Z,SINGLE 60602 FE3A CP 058 60604 CA0BED JP Z,SINGLE 60607 FE2C CP 044 60609 CA0BED JP Z,SINGLE 60612 FE2E CP 046 60614 CA0BED JP Z,SINGLE 60617 FE22 CP 034 60619 CA0BED JP Z,SINGLE 60622 FEFF CP 255 60624 CA0BED JP Z,SINGLE 60627 23 INC HL 60628 7E LD A,(HL) 60629 FE20 CP 032 60631 CA28ED JP Z,DOUBLE 60634 FE2C CP 044 60636 CA28ED JP Z,DOUBLE 60639 FE2E CP 046 60641 CA28ED JP Z,DOUBLE 60644 FE22 CP 034 60646 CA28ED JP Z,DOUBLE 60649 FE3A CP 058 60651 CA28ED JP Z,DOUBLE 60654 FEFF CP 255 60656 CA28ED JP Z,DOUBLE 60659 C3ACED JP TRIPLE 60662 23 SKIPSP INC HL 60663 7E LD A,(HL) 60664 FE20 CP 032 60666 CAF6EC JP Z,SKIPSP 60669 FE22 CP 034 60671 CAF6EC JP Z,SKIPSP 60674 FE3A CP 058 60676 CAF6EC JP Z,SKIPSP 60679 22C0EE LD (PTR),HL 60682 C9 RET 60683 2AC0EE SINGLE LD HL,(PTR) 60686 7E LD A,(HL) 60687 CD81ED CALL SETPTR 60690 ED5BCDEE LD DE,(VCV1) 60694 47 LD B,A 60695 1A LOOP1 LD A,(DE) 60696 FEFF CP 255 60698 C8 RET Z 60699 32C2EE LD (TPNO),A 60702 13 INC DE 60703 1A LD A,(DE) 60704 B8 CP B 60705 CA56ED JP Z,VBFND 60708 13 INC DE 60709 C317ED JP LOOP1 60712 2AC0EE DOUBLE LD HL,(PTR) 60715 7E LD A,(HL) 60716 ED5BCFEE LD DE,(VCV2) 60720 47 LD B,A 60721 23 INC HL 60722 7E LD A,(HL) 60723 4F LD C,A 60724 CD81ED CALL SETPTR 60727 1A LOOP3 LD A,(DE) 60728 FEFF CP 255 60730 C8 RET Z 60731 32C2EE LD (TPNO),A 60734 13 INC DE 60735 1A LD A,(DE) 60736 67 LD H,A 60737 13 INC DE 60738 1A LD A,(DE) 60739 6F LD L,A 60740 78 LD A,B 60741 BC CP H 60742 CA4DED JP Z,FND1 60745 13 INC DE 60746 C337ED JP LOOP3 60749 79 FND1 LD A,C 60750 BD CP L 60751 CA56ED JP Z,VBFND 60754 13 INC DE 60755 C337ED JP LOOP3 60758 3AC6EE VBFND LD A,(VB1) 60761 FE00 CP 000 60763 C265ED JP NZ,VB1DUN 60766 3AC2EE LD A,(TPNO) 60769 32C6EE LD (VB1),A 60772 C9 RET 60773 3AC7EE VB1DUN LD A,(VB2) 60776 FE00 CP 000 60778 C274ED JP NZ,VB2DUN 60781 3AC2EE LD A,(TPNO) 60784 32C7EE LD (VB2),A 60787 C9 RET 60788 3AC8EE VB2DUN LD A,(VB3) 60791 FE00 CP 000 60793 C0 RET NZ 60794 3AC2EE LD A,(TPNO) 60797 32C8EE LD (VB3),A 60800 C9 RET 60801 23 SETPTR INC HL 60802 F5 PUSH AF 60803 7E LOOP2 LD A,(HL) 60804 FE20 CP 032 60806 CAA6ED JP Z,NEWWD 60809 FE22 CP 034 60811 CAA6ED JP Z,NEWWD 60814 FE3A CP 058 60816 CAA6ED JP Z,NEWWD 60819 FEFF CP 255 60821 CAA7ED JP Z,LAST 60824 FE2C CP 044 60826 CAA7ED JP Z,LAST 60829 FE2E CP 046 60831 CAA7ED JP Z,LAST 60834 23 INC HL 60835 C383ED JP LOOP2 60838 23 NEWWD INC HL 60839 22C0EE LAST LD (PTR),HL 60842 F1 POP AF 60843 C9 RET 60844 2AC0EE TRIPLE LD HL,(PTR) 60847 7E LD A,(HL) 60848 CD64EE CALL AND 60851 FE01 CP 001 60853 C8 RET Z 60854 2AC0EE LD HL,(PTR) 60857 7E LD A,(HL) 60858 32D7EE LD (CHR1),A 60861 23 INC HL 60862 7E LD A,(HL) 60863 32D8EE LD (CHR2),A 60866 23 INC HL 60867 7E LD A,(HL) 60868 32D9EE LD (CHR3),A 60871 CD81ED CALL SETPTR 60874 ED5BD1EE LD DE,(VCV3) 60878 1A LOOP5 LD A,(DE) 60879 FEFF CP 255 60881 CAF5ED JP Z,DUNVBS 60884 CD49EE CALL SHORT1 60887 CAE0ED JP Z,VBMCH1 60890 13 INC DE 60891 13 INC DE 60892 13 INC DE 60893 C3CEED JP LOOP5 60896 CD54EE VBMCH1 CALL SHORT2 60899 CAEBED JP Z,VBMCH2 60902 13 INC DE 60903 13 INC DE 60904 C3CEED JP LOOP5 60907 CD5CEE VBMCH2 CALL SHORT3 60910 CA56ED JP Z,VBFND 60913 13 INC DE 60914 C3CEED JP LOOP5 60917 ED5BD3EE DUNVBS LD DE,(VCOB) 60921 1A LOOP6 LD A,(DE) 60922 FEFF CP 255 60924 CA20EE JP Z,DUNOBJ 60927 CD49EE CALL SHORT1 60930 CA0BEE JP Z,OBMCH1 60933 13 INC DE 60934 13 INC DE 60935 13 INC DE 60936 C3F9ED JP LOOP6 60939 CD54EE OBMCH1 CALL SHORT2 60942 CA16EE JP Z,OBMCH2 60945 13 INC DE 60946 13 INC DE 60947 C3F9ED JP LOOP6 60950 CD5CEE OBMCH2 CALL SHORT3 60953 CA7EEE JP Z,OBFND 60956 13 INC DE 60957 C3F9ED JP LOOP6 60960 ED5BD5EE DUNOBJ LD DE,(VCFK) 60964 1A LOOP7 LD A,(DE) 60965 FEFF CP 255 60967 C8 RET Z 60968 CD49EE CALL SHORT2 60971 CA34EE JP Z,FKMCH1 60974 13 INC DE 60975 13 INC DE 60976 13 INC DE 60977 C324EE JP LOOP7 60980 CD54EE FKMCH1 CALL SHORT2 60983 CA3FEE JP Z,FKMCH2 60986 13 INC DE 60987 13 INC DE 60988 C324EE JP LOOP7 60991 CD5CEE FKMCH2 CALL SHORT3 60994 CA9EEE JP Z,FKFND 60997 13 INC DE 60998 C324EE JP LOOP7 61001 32C2EE SHORT1 LD (TPNO),A 61004 13 INC DE 61005 1A LD A,(DE) 61006 47 LD B,A 61007 3AD7EE LD A,(CHR1) 61010 B8 CP B 61011 C9 RET 61012 13 SHORT2 INC DE 61013 1A LD A,(DE) 61014 47 LD B,A 61015 3AD8EE LD A,(CHR2) 61018 B8 CP B 61019 C9 RET 61020 13 SHORT3 INC DE 61021 1A LD A,(DE) 61022 47 LD B,A 61023 3AD9EE LD A,(CHR3) 61026 B8 CP B 61027 C9 RET 61028 FE41 AND CP 065 61030 C0 RET NZ 61031 23 INC HL 61032 7E LD A,(HL) 61033 FE4E CP 078 61035 C0 RET NZ 61036 23 INC HL 61037 7E LD A,(HL) 61038 FE44 CP 068 61040 C0 RET NZ 61041 CD8BEC CALL SETCOM 61044 3E01 LD A,001 61046 32DAEE LD (EXTR),A 61049 2B DEC HL 61050 CD81ED CALL SETPTR 61053 C9 RET 61054 3AC2EE OBFND LD A,(TPNO) 61057 47 LD B,A 61058 3ACBEE LD A,(OB1) 61061 B8 CP B 61062 C8 RET Z 61063 FE00 CP 000 61065 C291EE JP NZ,OB1DUN 61068 78 LD A,B 61069 32CBEE LD (OB1),A 61072 C9 RET 61073 3ACCEE OB1DUN LD A,(OB2) 61076 B8 CP B 61077 C8 RET Z 61078 FE00 CP 000 61080 C0 RET NZ 61081 78 LD A,B 61082 32CCEE LD (OB2),A 61085 C9 RET 61086 3AC2EE FKFND LD A,(TPNO) 61089 47 LD B,A 61090 3AC9EE LD A,(FK1) 61093 B8 CP B 61094 C8 RET Z 61095 FE00 CP 000 61097 C2B1EE JP NZ,FK1DUN 61100 78 LD A,B 61101 32C9EE LD (FK1),A 61104 C9 RET 61105 3ACAEE FK1DUN LD A,(FK2) 61108 B8 CP B 61109 C8 RET Z 61110 FE00 CP 000 61112 C0 RET NZ 61113 78 LD A,B 61114 32CAEE LD (FK2),A 61117 C9 RET 60318 START EQU 60318 60315 COUNT EQU 60315 61118 0000 NCOM DEFW 0 61120 0000 PTR DEFW 0 61122 00 TPNO DEFB 0 61123 00 MORE DEFB 0 61124 00 TELL DEFB 0 61125 00 PERS DEFB 0 61126 00 VB1 DEFB 0 61127 00 VB2 DEFB 0 61128 00 VB3 DEFB 0 61129 00 FK1 DEFB 0 61130 00 FK2 DEFB 0 61131 00 OB1 DEFB 0 61132 00 OB2 DEFB 0 61133 0000 VCV1 DEFW 0 61135 0000 VCV2 DEFW 0 61137 0000 VCV3 DEFW 0 61139 0000 VCOB DEFW 0 61141 0000 VCFK DEFW 0 61143 00 CHR1 DEFB 0 61144 00 CHR2 DEFB 0 61145 00 CHR3 DEFB 0 61146 00 EXTR DEFB 0 - - - - - Part 3: December 1986 Alan Davis puts the finishing touches to his Venturespeak Editor. The story so far: You are in your living room. You can see a ZX Spectrum, several copies of ZX Computing Monthly, and a large chunk of VENTURESPEAK machine code saved on tape. What now? WRITE ADVENTURE You can't do that. EXAMINE TAPE There's something missing. HELP You need the VENTURESPEAK editor ... Indeed you do, and here it is. Type in Listing 1 and save it to tape with SAVE "EDITOR" LINE 1. Rewind the tape and verify, and then stop the tape at that point - we need to save the code from the previous articles after the editor program so that it can be loaded automatically (see Line 4 of Listing 1). To do this, enter CLEAR 59999 as a direct command, load in the 1150 bytes you stashed away last month, and save it directly after the EDITOR program with SAVE "V-SPEAK" CODE 60000,1150. Figure 1 - sample screens from the editor --------------------------------------------- 1(A) Main Menu SELECT: 1: Edit verbs 2: Edit objects 3: Edit people 4: POKE vocabulary into memory 5: Save vocab. array to tape 6: Load vocab. array from tape 7: Hard copy of vocabulary 8: Test Venturespeak system --------------------------------------------- 1(B) Verbs Editor, Page 0 WORD NO. SAY 1 TEL 1 ASK 1 TAL 1 L 2 LOO 2 N 3 NOR 3 VERBS S 4 >> SOU 4 << Page 0 E 5 EAS 5 W 6 WES 6 IN 7 ENT 7 OUT 8 LEA 8 U 9 UP 9 6=cursor up 7=cursor down 5&8=change page q=main menu ENTER=add/edit vocabulary item --------------------------------------------- 1(C) Verbs Editor, Page 1 WORD NO. CLI 9 ASC 9 D 10 DOW 10 DES 10 GET 11 TAK 11 PIC 11 VERBS DRO 12 PUT 12 Page 1 EXA 13 REA 13 GIV 14 OFF 14 FIG 15 KIL 15 ATT 15 HIT 15 >> << 6=cursor up 7=cursor down 5&8=change page q=main menu ENTER=add/edit vocabulary item --------------------------------------------- 1(D) Generator of parser code with vocabulary POKEing vocabulary bytes 61354 255 Vocabulary stored from 61150 to 61354 inclusive PRESS "Q" TO RETURN TO MENU OR ANY OTHER KEY TO SAVE COMPLETE PARSER PLUS VOCABULARY (1355 bytes starting at 60000) --------------------------------------------- All set? OK - rewind the tape, type LOAD "", and wait for the whole thing to load in. You should then be greeted on screen with the main menu, which should look like Figure 1(a). It does? So far, so good. Press "1" to select the "Edit verbs" option, and you should get a screen looking like Figure 1(b) - except that of course none of the verbs will be there since you haven't put any in yet! Press ENTER, and you'll be asked to type in a verb - reply with the verb TELL. Then you'll be asked to type in the number of this verb - respond with the number 1. Hey Presto: the entry TEL#1 will appear on screen, with the cursor automatically moving down a line ready for your next input. (Remember that all words are truncated to their first three letters, regardless of how many you actually type in.) Try entering other verbs in the same way - use Figures 1(b) and 1(c) as a guide, if you like - and notice how the editor looks after you when you reach the bottom of the screen by clearing "Page 0" and starting afresh at the top of "Page 1". 'Ah yes," you say, "That's all very well, but I happened to make a mistake on Page 0. What can I do about that?" Have no fear. At any stage you can add new entries, or change existing entries, by skipping through them using the 5, 6, 7 and 8 keys. Use keys 5 and 8 to change pages (5 decreases page number while 8 increases it), and use keys 6 and 7 to move the cursor up and down the list. Position the cursor at the entry you want to change, press ENTER, and type in the amendment. The old entry will be deleted, and the new one inserted in its place. If you want to delete an entry completely, without replacement, just type a space when the program prompts you to enter a verb. It's probably obvious (but I'll say it . anyway), that since entries are always inserted at the current cursor position, you'll need to skip through to the end of the file to make new additions after making an amendment - or if you prefer, you can return to the main menu and select option "1" again, when the program will automatically position the cursor at the end of the file for you. Verbs This is a good place to remind you that verbs related to speech, such as TELL, SAY or ASK, must be assigned the number 1. Apart from this you can allocate any number up to 254 to any verb (allocating the same number to synonyms, of course). The order in which they occur in the list is quite irrelevant. I should also point out that any abbreviation you'd like the parser to accept (such as "N" for "NORTH") must be entered separately as a synonym in its own right - there are plenty of examples of this in Figures 1(b) and 1(c). Once you've become used to the editing system, press "q" to return to the main menu, and try selecting options 2 and 3. You'll find that the method of adding entries to the "people" and "objects" vocabulary files is just the same; the program asks you first for the word, then for its number, whenever you press ENTER. Easy, isn't it? Don't worry about making illegal entries by mistake - the editor is very extensively error-trapped, and it should be virtually impossible to crash it by accident. What about those other options on the main menu? Option 4 ("POKE vocabulary") does require a little explanation. You see, in any of the 3 "Edit" modes, all you're actually doing is manipulating the contents of a temporary file - In fact it's a BASIC array, v$(). When you're happy that all the vocabulary you need has been added, selecting option 4 causes the editor to POKE your vocabulary into memory above RAMTOP, making the appropriate modifications to the VENTURESPEAK machine code as necessary. Once this is finished, the program will offer to save the complete parser code (including vocabulary) to tape, informing you where in memory it resided, and how long it is. (See Figure 1(d)). This is the final chunk of code which you'll subsequently incorporate into your adventure. Files Options 5 and 6 on the main menu improve the versatility of the editor program, allowing you to save and load the temporary vocabulary file, v$(), to and from tape. Suppose, for example, that you've just spent a session entering vocabulary, but wish to stop and continue some other time. All you need to do is save what you've done so far using option 5 and then load it back into the editor at the start of your next session, using option 6. Actually, I strongly recommend that you save the temporary file as well as the completed code even when you think you've finished, just as a matter of course. The reason for this is that when you come to play-test your adventure game eventually, you're almost certain to want to make additions or modifications to the vocabulary at some stage - and there's no way of modifying the code itself from within the editor. However, if you've saved the temporary array there's no problem. Just load it into the editor using option 6, make your amendments, and then generate a fresh batch of code (using option 4) to replace the old one. Of the other options on the main menu, number 7 is self- explanatory. This gives you a hard copy of the entire vocabulary file for future reference. The only comment worth making here is that the print-out is produced from the temporary file v$(), rather than from the finished code - so you can get hard copy whether or not you've previously used option 4 to POKE in the vocabulary. The last option, number 8, allows you to test the command analysis system in situ, but note that the editor won't let you try this unless you've first POKEd in the vocabulary by using option 4. I went into details of the method of command analysis last month, and now you can try out some of those examples for yourself using this facility. Just type in your command when you see the little "prompt" symbol, and the system will do a complete analysis for you. There are one or two other points I should make about the editor itself. With the program as written, each of the three vocabulary files can hold up to 200 entries - giving you a maximum vocabulary of 600 words. This will almost certainly give you adequate scope - and increasing the limit needlessly will merely increase saving and loading times for the temporary array. However, there's nothing to stop you increasing the limit if you wish to do so; you simply need to change the value assigned to the variable "max" in line 10 of listing 1. It should be assigned a value of (1 + some multiple of 20); LET max = 401, for instance, will give you scope for up to 400 entries in each file. One further point about the editor is that you can use the POKE vocabulary option as many times as you like within a session; it will just overwrite whatever went before. Now that you have the editor, you're in the happy position of being able to produce a machine code parser for any adventure you care to write - but a few tips about incorporating it into a BASIC adventure program may not go amiss. You'll need to CLEAR 59999, of course, so that your parser can be safely loaded in at address 60000. If you've written adventures in BASIC already then you should have no problems, but I've given in Listing 2 [file: outline] a suggested outline structure (it is no more than a mere outline) which you might find helpful. The significance of the various symbols was explained in last month's article. Essentially the purpose of the parser is to translate an English sentence into a series of numbers on which condition tests can be performed, and of course the use you make of it lies entirely in your own hands. Nevertheless, there are methods of working which lend themselves well to the VENTURESPEAK system. Objects For instance, one sensible approach might be to store all your subject descriptions in a series of DATA statements in exactly the same order as they appear in the vocabulary file. Thus we might have lines such as: 9001 DATA "bright sword" 9002 DATA "sharp dagger" 9003 DATA "long spear' 9004 DATA "large chest" etc. You'd then arrange your vocabulary so that "SWO" is assigned the number 1, "DAG" the number 2, "SPE" the number 3, and "CHE" the number 4. In this way, values of ob1 and ob2 extracted from the parser can be related directly to specific data items for printing messages. Thus a command like "PUT THE SWORD IN THE CHEST" when analysed would give ob1=1 and ob2=4. Then in your "putting" subroutine you'd have a line like: RESTORE (9000+ob1): READ x$: RESTORE (9000+ob2): READ y$: PRINT "You put the ";x$;" in the ";y$;"." This would produce the message "You put the bright sword in the large chest" - assuming of course that the necessary logical tests have been satisfied. You can use similar tricks for dealing with more complex commands involving other characters. Thus you might list characters' names in a second set of DATA statements, eg 8001 DATA "Sam" 8002 DATA "Fred" etc ensuring that SAM is allocated the number 1 in the "people" vocabulary file, and FRE(D) the number 2. A command such as "ASK SAM TO GIVE THE SWORD TO FRED" can then be conveniently streamlined. The analysis would give tell=1, pers=1, fk1=2, ob1=1, and vb1=whatever number you assigned to the verb "GIVE". In the speech subroutine, after the appropriate checks to ensure that Sam is indeed present, you'd have a line like: RESTORE (8000+pers): READ p$: PRINT "You talk to ";p$;"." Subsequently vb1 is inspected, resulting in the "giving" subroutine being called. Here would be another series of condition tests (Is the object available for giving? Is fk1 - Fred - present?) leading ultimately to a line like this: RESTORE (9000+ob1): READ x$: RESTORE (8000+fk1): READ y$: LET p$=(p$ AND tell)+("You" AND NOT tell): PRINT p$;" give";"s" AND tell;" the ";x$;" to ";y$;"." In this particular example you'd then see on screen the message: "You talk to Sam. Sam gives the bright sword to Fred." Of course you could embellish this as much as you like - writing convincing conversation routines is an important part of the fun! Notice that the line suggested above will deal with the situation no matter who is doing the giving; if the variable "tell" is zero, you'd just get: "You give the bright sword to Fred". So there you are - the VENTURESPEAK parser is at your disposal. For the rest - the creation of that wonderful adventure which will outshine "Sherlock" - over to you ...