The Art of
ASSEMBLY LANGUAGE PROGRAMMING

Chapter Sixteen (Part 11)

Table of Content

Chapter Seventeen  

CHAPTER SIXTEEN:
PATTERN MATCHING (Part 12)

16.8.5 - The "MADVENTURE" Game

16.8.5 The "MADVENTURE" Game

Computer games are a perfect example of programs that often use pattern matching. One class of computer games in general, the adventure game, is a perfect example of games that use pattern matching. An adventure style game excepts English-like commands from the user, parses these commands, and acts upon them. In this section we will develop an adventure game shell. That is, it will be a reasonably functional adventure style game, capable of accepting and processing user commands. All you need do is supply a story line and a few additional details to develop a fully functioning adventure class game.

An adventure game usually consists of some sort of maze through which the player moves. The program processes commands like go north or go right to move the player through the maze. Each move can deposit the player in a new room of the game. Generally, each room or area contains objects the player can interact with. This could be reward objects such as items of value or it could be an antagonistic object like a monster or enemy player.

Usually, an adventure game is a puzzle of some sort. The player finds clues and picks up useful object in one part of the maze to solve problems in other parts of the maze. For example, a player could pick up a key in one room that opens a chest in another; then the player could find an object in the chest that is useful elsewhere in the maze. The purpose of the game is to solve all the interlocking puzzles and maximize one's score (however that is done). This text will not dwell upon the subtleties of game design; that is a subject for a different text. Instead, we'll look at the tools and data structures required to implement the game design.

The Madventure game's use of pattern matching is quite different from the previous examples appearing in this chapter. In the examples up to this point, the matching routines specifically checked the validity of an input string; Madventure does not do this. Instead, it uses the pattern matching routines to simply determine if certain key words appear on a line input by the user. The program handles the actual parsing (determining if the command is syntactically correct). To understand how the Madventure game does this, it would help if we took a look at how to play the Madventure game.

The Madventure prompts the user to enter a command. Unlike the original adventure game that required commands like "GO NORTH" (with no other characters other than spaces as part of the command), Madventure allows you to write whole sentences and then it attempts to pick out the key words from those sentences. For example, Madventure accepts the "GO NORTH" command; however, it also accepts commands like "North is the direction I want to go" and "I want to go in the north direction." Madventure doesn't really care as long as it can find "GO" and "NORTH" somewhere on the command line. This is a little more flexible that the original Adventure game structure. Of course, this scheme isn't infallible, it will treat commands like "I absolutely, positively, do NOT want to go anywhere near the north direction" as a "GO NORTH" command. Oh well, the user almost always types just "GO NORTH" anyway.

A Madventure command usually consists of a noun keyword and a verb keyword. The Madventure recognizes six verbs and fourteen nouns. The verbs are

verbs  go | get | drop | inventory | quit | help

The nouns are

nouns north | south | east | west | lime | beer | card | sign | program | homework | money | form | coupon

Obviously, Madventure does not allow all combinations of verbs and nouns. Indeed, the following patterns are the only legal ones:

LegalCmds go direction | get item | drop item | inventory | quit | help

direction north | south | east | west

item lime | beer | card | sign | program | homework | money | form | coupon

However, the pattern does not enforce this grammar. It just locates a noun and a verb on the line and, if found, sets the noun and verb variables to appropriate values to denote the keywords it finds. By letting the main program handle the parsing, the program is somewhat more flexible.

There are two main patterns in the Madventure program: NounPat and VerbPat. These patterns match words (nouns or verbs) using a regular expression like the following:

(ARB* ` ` | ) word (` ` | EOS)

This regular expression matches a word that appears at the beginning of a sentence, at the end of a sentence, anywhere in the middle of a sentence, or a sentence consisting of a single word. Madventure uses a macro (MatchNoun or MatchVerb) to create an expression for each noun and verb in the above expression.

To get an idea of how Madvent processes words, consider the following VerbPat pattern:

VerbPat         pattern         {sl_match2, MatchGo}
                MatchVerb       MatchGO, MatchGet, "GO", 1
                MatchVerb       MatchGet, MatchDrop, "GET", 2
                MatchVerb       MatchDrop, MatchInv, "DROP", 3
                MatchVerb       MatchInv, MatchQuit, "INVENTORY", 4
                MatchVerb       MatchQuit, MatchHelp, "QUIT", 5
                MatchVerb       MatchHelp, 0, "HELP", 6

The MatchVerb macro expects four parameters. The first is an arbitrary pattern name; the second is a link to the next pattern in the list; the third is the string to match, and the fourth is a number that the matching routines will store into the verb variable if that string matches (by default, the verb variable contains zero). It is very easy to add new verbs to this list. For example, if you wanted to allow "run" and "walk" as synonyms for the "go" verb, you would just add two patterns to this list:

VerbPat         pattern         {sl_match2, MatchGo}
                MatchVerb       MatchGO, MatchGet, "GO", 1
                MatchVerb       MatchGet, MatchDrop, "GET", 2
                MatchVerb       MatchDrop, MatchInv, "DROP", 3
                MatchVerb       MatchInv, MatchQuit, "INVENTORY", 4
                MatchVerb       MatchQuit, MatchHelp, "QUIT", 5
                MatchVerb       MatchHelp, MatchRun, "HELP", 6
                MatchVerb       MatchRun, MatchWalk, "RUN", 1
                MatchVerb       MatchWalk, 0, "WALK", 1

There are only two things to consider when adding new verbs: first, don't forget that the next field of the last verb should contain zero; second, the current version of Madventure only allows up to seven verbs. If you want to add more you will need to make a slight modification to the main program (more on that, later). Of course, if you only want to create synonyms, as we've done here, you simply reuse existing verb values so there is no need to modify the main program.

When you call the match routine and pass it the address of the VerbPat pattern, it scans through the input string looking for the first verb. If it finds that verb ("GO") it sets the verb variable to the corresponding verb value at the end of the pattern. If match cannot find the first verb, it tries the second. If that fails, it tries the third, and so on. If match cannot find any of the verbs in the input string, it does not modify the verb variable (which contains zero). If there are two or more of the above verbs on the input line, match will locate the first verb in the verb list above. This may not be the first verb appearing on the line. For example, if you say "Let's get the money and go north" the match routine will match the "go" verb, not the "get" verb. By the same token, the NounPat pattern would match the north noun, not the money noun. So this command would be identical to "GO NORTH."

The MatchNoun is almost identical to the MatchVerb macro; there is, however, one difference - the MatchNoun macro has an extra parameter which is the name of the data structure representing the given object (if there is one). Basically, all the nouns (in this version of Madventure) except NORTH, SOUTH, EAST, and WEST have some sort of data structure associated with them.

The maze in Madventure consists of nine rooms defined by the data structure:

Room            struct
north           word    ?
south           word    ?
west            word    ?
east            word    ?
ItemList        word    MaxWeight dup (?)
Description     word    ?
Room            ends

The north, south, west, and east fields contain near pointers to other rooms. The program uses the CurRoom variable to keep track of the player's current position in the maze. When the player issues a "GO" command of some sort, Madventure copies the appropriate value from the north, south, west, or east field to the CurRoom variable, effectively changing the room the user is in. If one of these pointers is NULL, then the user cannot move in that direction.

The direction pointers are independent of one another. If you issue the command "GO NORTH" and then issue the command "GO SOUTH" upon arriving in the new room, there is no guarantee that you will wind up in the original room. The south field of the second room may not point at the room that led you there. Indeed, there are several cases in the Madventure game where this occurs.

The ItemList array contains a list of near pointers to objects that could be in the room. In the current version of this game, the objects are all the nouns except north, south, east, and west. The player can carry these objects from room to room (indeed, that is the major purpose of this game). Up to MaxWeight objects can appear in the room (MaxWeight is an assembly time constant that is currently four; so there are a maximum of four items in any one given room). If an entry in the ItemList is non-NULL, then it is a pointer to an Item object. There may be zero to MaxWeight objects in a room.

The Description field contains a pointer to a zero terminated string that describes the room. The program prints this string each time through the command loop to keep the player oriented.

The second major data type in Madventure is the Item structure. This structure takes the form:

Item            struct
Value           word    ?
Weight          word    ?
Key             word    ?
ShortDesc       word    ?
LongDesc        word    ?
WinDesc         word    ?
Item            ends

The Value field contains an integer value awarded to the player when the player drops this object in the appropriate room. This is how the user scores points.

The Weight field usually contains one or two and determines how much this object "weighs." The user can only carry around MaxWeight units of weight at any one given time. Each time the user picks up an object, the weight of that object is added to the user's total weight. When the user drops an object, Madventure subtracts the object's weight from the total.

The Key field contains a pointer to a room associated with the object. When the user drops the object in the Key room, the user is awarded the points in the Value field and the object disappears from the game. If the user drops the object in some other room, the object stays in that room until the user picks it up again.

The ShortDesc, LongDesc, and WinDesc fields contain pointers to zero terminated strings. Madventure prints the ShortDesc string in response to an INVENTORY command. It prints the LongDesc string when describing a room's contents. It prints the WinDesc string when the user drops the object in its Key room and the object disappears from the game.

The Madventure main program is deceptively simple. Most of the logic is hidden in the pattern matching routines and in the parsing routine. We've already discussed the pattern matching code; the only important thing to remember is that it initializes the noun and verb variables with a value uniquely identifying each noun and verb. The main program's logic uses these two values as an index into a two dimensional table that takes the following form:

Madventure Noun/Verb Table
  No Verb GO GET DROP Inventory Quit Help
No Noun - - - - Inventory Quit Help
North - Do North - - - - -
South - Do South - - - - -
East - Do East - - - - -
West - Do West - - - - -
Lime - - Get Item Drop Item - - -
Beer - - Get Item Drop Item - - -
Card - - Get Item Drop Item - - -
Sign - - Get Item Drop Item - - -
Program - - Get Item Drop Item - - -
Homework - - Get Item Drop Item - - -
Money - - Get Item Drop Item - - -
Form - - Get Item Drop Item - - -
Coupon - - Get Item Drop Item - - -

The empty entries in this table correspond to illegal commands. The other entries are addresses of code within the main program that handles the given command.

To add more nouns (objects) to the game, you need only extend the NounPat pattern and add additional rows to the table (of course, you may need to add code to handle the new objects if they are not easily handled by the routines above). To add new verbs you need only extended the VerbPat pattern and add new columns to this table.

Other than the goodies mentioned above, the rest of the program utilizes techniques appearing throughout this and previous chapters. The only real surprising thing about this program is that you can implement a fairly complex program with so few lines of code. But such is the advantage of using pattern matching techniques in your assembly language programs.

; MADVENT.ASM
;
; This is a "shell" of an adventure game that you can use to create
; your own adventure style games.

                .xlist
                .286
                include         stdlib.a
                includelib      stdlib.lib
                matchfuncs
                .list

dseg            segment         para public 'data'

; Equates:

NULL            equ     0
MaxWeight       equ     4       ;Max weight user can carry at one time.


; The "ROOM" data structure defines a room, or area, where a player can
; go. The NORTH, SOUTH, EAST, and WEST fields contain the address of
; the rooms to the north, south, east, and west of the room. The game
; transfers control to the room whose address appears in these fields
; when the player supplies a GO NORTH, GO SOUTH, etc., command.
;
; The ITEMLIST field contains a list of pointers to objects appearing
; in this room. In this game, the user can pick up and drop these
; objects (if there are any present).
;
; The DESCRIPTION field contains a (near) address of a short description
; of the current room/area.

Room            struct
north           word    ?       ;Near pointers to other structures where
south           word    ?       ; we will wind up on the GO NORTH, GO SOUTH,
west            word    ?       ; etc., commands.
east            word    ?

ItemList        word    MaxWeight dup (?)

Description     word    ?       ;Description of room.
Room            ends


; The ITEM data structure describes the objects that may appear
; within a room (in the ITEMLIST above). The VALUE field contains
; the number of points this object is worth if the user drops it
; off in the proper room (i.e, solves the puzzle). The WEIGHT
; field provides the weight of this object. The user can only
; carry four units of weight at a time. This field is usually
; one, but may be more for larger objects. The KEY field is the
; address of the room where this object must be dropped to solve
; the problem. The SHORTDESC field is a pointer to a string that
; the program prints when the user executes an INVENTORY command.
; LONGDESC is a pointer to a string the program prints when des-
; cribing the contents of a room. The WINDESC field is a pointer
; to a string that the program prints when the user solves the
; appropriate puzzle.

Item            struct
Value           word    ?
Weight          word    ?
Key             word    ?
ShortDesc       word    ?
LongDesc        word    ?
WinDesc         word    ?
Item            ends


; State variables for the player:

CurRoom         word    Room1            ;Room the player is in.
ItemsOnHand     word    MaxWeight dup (?) ;Items the player carries.
CurWeight       word    0                ;Weight of items carried.
CurScore        word    15               ;Player's current score.
TotalCounter    word    9                ;Items left to place.
Noun            word    0                ;Current noun value.
Verb            word    0                ;Current verb value.
NounPtr         word    0                ;Ptr to current noun item.


; Input buffer for commands

InputLine       byte    128 dup (?)

; The following macros generate a pattern which will match a single word
; which appears anywhere on a line. In particular, they match a word
; at the beginning of a line, somewhere in the middle of the line, or
; at the end of a line. This program defines a word as any sequence
; of character surrounded by spaces or the beginning or end of a line.
;
; MatchNoun/Verb matches lines defined by the regular expression:
;
;       (ARB* ' ' | e) string (' ' | EOS)

MatchNoun       macro   Name, next, WordString, ItemVal, ItemPtr
                local   WS1, WS2, WS3, WS4
                local   WS5, WS6, WordStr

Name            Pattern {sl_match2, WS1, next}
WS1             Pattern {MatchStr, WordStr, WS2, WS5}
WS2             Pattern {arb,0,0,WS3}
WS3             Pattern {Matchchar, ' ',0, WS4}
WS4             Pattern {MatchStr, WordStr, 0, WS5}
WS5             Pattern {SetNoun,ItemVal,0,WS6}
WS6             Pattern {SetPtr, ItemPtr,0,MatchEOS}
WordStr         byte    WordString
                byte    0
                endm


MatchVerb       macro   Name, next, WordString, ItemVal
                local   WS1, WS2, WS3, WS4
                local   WS5, WordStr

Name            Pattern {sl_match2, WS1, next}
WS1             Pattern {MatchStr, WordStr, WS2, WS5}
WS2             Pattern {arb,0,0,WS3}
WS3             Pattern {Matchchar, ' ',0, WS4}
WS4             Pattern {MatchStr, WordStr, 0, WS5}
WS5             Pattern {SetVerb,ItemVal,0,MatchEOS}
WordStr         byte    WordString
                byte    0
                endm




; Generic patterns which most of the patterns use:

MatchEOS        Pattern {EOS,0,MatchSpc}
MatchSpc        Pattern {MatchChar,' '}


; Here are the list of nouns allowed in this program.

NounPat         pattern         {sl_match2, MatchNorth}

                MatchNoun       MatchNorth, MatchSouth, "NORTH", 1, 0
                MatchNoun       MatchSouth, MatchEast, "SOUTH", 2, 0
                MatchNoun       MatchEast, MatchWest, "EAST", 3, 0
                MatchNoun       MatchWest, MatchLime, "WEST", 4, 0
                MatchNoun       MatchLime, MatchBeer, "LIME", 5, Item3
                MatchNoun       MatchBeer, MatchCard, "BEER", 6, Item9
                MatchNoun       MatchCard, MatchSign, "CARD", 7, Item2
                MatchNoun       MatchSign, MatchPgm, "SIGN", 8, Item1
                MatchNoun       MatchPgm, MatchHW, "PROGRAM", 9, Item7
                MatchNoun       MatchHW, MatchMoney, "HOMEWORK", 10, Item4
                MatchNoun       MatchMoney, MatchForm, "MONEY", 11, Item5
                MatchNoun       MatchForm, MatchCoupon, "FORM", 12, Item6
                MatchNoun       MatchCoupon, 0, "COUPON", 13, Item8


; Here is the list of allowable verbs.

VerbPat         pattern         {sl_match2, MatchGo}

                MatchVerb       MatchGO, MatchGet, "GO", 1
                MatchVerb       MatchGet, MatchDrop, "GET", 2
                MatchVerb       MatchDrop, MatchInv, "DROP", 3
                MatchVerb       MatchInv, MatchQuit, "INVENTORY", 4
                MatchVerb       MatchQuit, MatchHelp, "QUIT", 5
                MatchVerb       MatchHelp, 0, "HELP", 6

; Data structures for the "maze".

Room1           room    {Room1, Room5, Room4, Room2,
                         {Item1,0,0,0},
                         Room1Desc}

Room1Desc       byte    "at the Commons",0

Item1           item    {10,2,Room3,GS1,GS2,GS3}
GS1             byte    "a big sign",0
GS2             byte    "a big sign made of styrofoam with funny "
                byte    "letters on it.",0
GS3             byte    "The ETA PI Fraternity thanks you for return"
                byte    "ing their sign, they",cr,lf
                byte    "make you an honorary life member, as long as "
                byte    "you continue to pay",cr,lf
                byte    "your $30 monthly dues, that is.",0

Room2           room    {NULL, Room5, Room1, Room3,
                         {Item2,0,0,0},
                         Room2Desc}

Room2Desc       byte    'at the "C" on the hill above campus',0

Item2           item    {10,1,Room1,LC1,LC2,LC3}
LC1             byte    "a lunch card",0
LC2             byte    "a lunch card which someone must have "
                byte    "accidentally dropped here.", 0
LC3             byte    "You get a big meal at the Commons cafeteria"
                byte    cr,lf
                byte    "It would be a good idea to go visit the "
                byte    "student health center",cr,lf
                byte    "at this time.",0

Room3           room    {NULL, Room6, Room2, Room2,
                         {Item3,0,0,0},
                         Room3Desc}

Room3Desc       byte    "at ETA PI Frat House",0

Item3           item    {10,2,Room2,BL1,BL2,BL3}
BL1             byte    "a bag of lime",0
BL2             byte    "a bag of baseball field lime which someone "
                byte    "is obviously saving for",cr,lf
                byte    "a special occasion.",0
BL3             byte    "You spread the lime out forming a big '++' "
                byte    "after the 'C'",cr,lf
                byte    "Your friends in Computer Science hold you "
                byte    "in total awe.",0

Room4           room    {Room1, Room7, Room7, Room5,
                         {Item4,0,0,0},
                         Room4Desc}

Room4Desc       byte    "in Dr. John Smith's Office",0

Item4           item    {10,1,Room7,HW1,HW2,HW3}
HW1             byte    "a homework assignment",0
HW2             byte    "a homework assignment which appears to "
                byte    "to contain assembly language",0
HW3             byte    "The grader notes that your homework "
                byte    "assignment looks quite",cr,lf
                byte    "similar to someone else's assignment "
                byte    "in the class and reports you",cr,lf
                byte    "to the instructor.",0

Room5           room    {Room1, Room9, Room7, Room2,
                        {Item5,0,0,0},
                         Room5Desc}

Room5Desc       byte    "in the computer lab",0

Item5           item    {10,1,Room9,M1,M2,M3}
M1              byte    "some money",0
M2              byte    "several dollars in an envelope in the "
                byte    "trashcan",0
M3              byte    "The waitress thanks you for your "
                byte    "generous tip and gets you",cr,lf
                byte    "another pitcher of beer. "
                byte    "Then she asks for your ID.",cr,lf
                byte    "You are at least 21 aren't you?",0

Room6           room    {Room3, Room9, Room5, NULL,
                         {Item6,0,0,0},
                         Room6Desc}

Room6Desc       byte    "at the campus book store",0

Item6           item    {10,1,Room8,AD1,AD2,AD3}
AD1             byte    "an add/drop/change form",0
AD2             byte    "an add/drop/change form filled out for "
                byte    "assembly to get a letter grade",0
AD3             byte    "You got the form in just in time. "
                byte    "It would have been a shame to",cr,lf
                byte    "have had to retake assembly because "
                byte    "you didn't realize you needed to ",cr,lf
                byte    "get a letter grade in the course.",0

Room7           room    {Room1, Room7, Room4, Room8,
                         {Item7,0,0,0},
                         Room7Desc}

Room7Desc       byte    "in the assembly lecture",0

Item7           item    {10,1,Room5,AP1,AP2,AP3}
AP1             byte    "an assembly language program",0
AP2             byte    "an assembly language program due in "
                byte    "the assemblylanguage class.",0
AP3             byte    "The sample program the instructor gave "
                byte    "you provided all the information",cr,lf
                byte    "you needed to complete your assignment. "
                byte    "You finish your work and",cr,lf
                byte    "head to the local pub to celebrate."
                byte    cr,lf,0

Room8           room    {Room5, Room6, Room7, Room9,
                         {Item8,0,0,0},
                         Room8Desc}

Room8Desc       byte     "at the Registrar's office",0

Item8           item    {10,1,Room6,C1,C2,C3}
C1              byte    "a coupon",0
C2              byte    "a coupon good for a free text book",0
C3              byte    'You get a free copy of "Cliff Notes for '
                byte    'The Art of Assembly',cr,lf
                byte    'Language Programming" Alas, it does not '
                byte    "provide all the",cr,lf
                byte    "information you need for the class, so you "
                byte    "sell it back during",cr,lf
                byte    "the book buy-back period.",0



Room9           room    {Room6, Room9, Room8, Room3,
                         {Item9,0,0,0},
                         Room9Desc}

Room9Desc       byte    "at The Pub",0
Item9           item    {10,2,Room4,B1,B2,B3}
B1              byte    "a pitcher of beer",0
B2              byte    "an ice cold pitcher of imported beer",0
B3              byte    "Dr. Smith thanks you profusely for your "
                byte    "good taste in brews.",cr,lf
                byte    "He then invites you to the pub for a "
                byte    "round of pool and",cr,lf
                byte    "some heavy duty hob-nobbing, "
                byte    "CS Department style.",0

dseg            ends


cseg            segment         para public 'code'
                assume          ds:dseg


; SetNoun-      Copies the value in SI (the matchparm parameter) to the
;               NOUN variable.

SetNoun         proc    far
                push    ds
                mov     ax, dseg
                mov     ds, ax
                mov     Noun, si
                mov     ax, di
                stc
                pop     ds
                ret
SetNoun         endp


; SetVerb-      Copies the value in SI (the matchparm parameter) to the
;               VERB variable.

SetVerb         proc    far
                push    ds
                mov     ax, dseg
                mov     ds, ax
                mov     Verb, si
                mov     ax, di
                stc
                pop     ds
                ret
SetVerb         endp

; SetPtr-       Copies the value in SI (the matchparm parameter) to the
;               NOUNPTR variable.

SetPtr          proc    far
                push    ds
                mov     ax, dseg
                mov     ds, ax
                mov     NounPtr, si
                mov     ax, di
                stc
                pop     ds
                ret
SetPtr          endp

; CheckPresence-
;               BX points at an item. DI points at an item list. This
;               routine checks to see if that item is present in the
;               item list. Returns Carry set if item was found,
;               clear if not found.

CheckPresence   proc

; MaxWeight is an assembly-time adjustable constant that determines
; how many objects the user can carry, or can be in a room, at one
; time. The following repeat macro emits "MaxWeight" compare and
; branch sequences to test each item pointed at by DS:DI.

ItemCnt         =       0
                repeat  MaxWeight
                cmp     bx, [di+ItemCnt]
                je      GotIt

ItemCnt         =       ItemCnt+2
                endm

                clc
                ret

GotIt:          stc
                ret
CheckPresence   endp

; RemoveItem-   BX contains a pointer to an item. DI contains a pointer
;               to an item list which contains that item. This routine
;               searches the item list and removes that item from the
;               list. To remove an item from the list, we need only
;               store a zero (NULL) over the top of its pointer entry
;               in the list.

RemoveItem      proc

; Once again, we use the repeat macro to automatically generate a chain
; of compare, branch, and remove code sequences for each possible item
; in the list.

ItemCnt         =       0
                repeat  MaxWeight
                local   NotThisOne
                cmp     bx, [di+ItemCnt]
                jne     NotThisOne
                mov     word ptr [di+ItemCnt], NULL
                ret
NotThisOne:
ItemCnt         =       ItemCnt+2
                endm

                ret
RemoveItem      endp


; InsertItem-   BX contains a pointer to an item, DI contains a pointer to
;               and item list. This routine searches through the list for
;               the first empty spot and copies the value in BX to that point.
;               It returns the carry set if it succeeds. It returns the
;               carry clear if there are no empty spots available.

InsertItem      proc

ItemCnt         =       0
                repeat  MaxWeight
                local   NotThisOne
                cmp     word ptr [di+ItemCnt], 0
                jne     NotThisOne
                mov     [di+ItemCnt], bx
                stc
                ret
NotThisOne:
ItemCnt         =       ItemCnt+2
                endm

                clc
                ret
InsertItem      endp

; LongDesc- Long description of an item.
; DI points at an item - print the long description of it.

LongDesc        proc
                push    di
                test    di, di
                jz      NoDescription
                mov     di, [di].item.LongDesc
                puts
                putcr
NoDescription:  pop     di
                ret
LongDesc        endp


; ShortDesc- Print the short description of an object.
; DI points at an item (possibly NULL). Print the short description for it.

ShortDesc       proc
                push    di
                test    di, di
                jz      NoDescription
                mov     di, [di].item.ShortDesc
                puts
                putcr
NoDescription:  pop     di
                ret
ShortDesc       endp

; Describe:     "CurRoom" points at the current room. Describe it and its
;               contents.

Describe        proc
                push    es
                push    bx
                push    di
                mov     di, ds
                mov     es, di

                mov     bx, CurRoom
                mov     di, [bx].room.Description
                print
                byte    "You are currently ",0
                puts
                putcr
                print
                byte    "Here you find the following:",cr,lf,0

; For each possible item in the room, print out the long description
; of that item. The repeat macro generates a code sequence for each
; possible item that could be in this room.

ItemCnt         =       0
                repeat  MaxWeight
                mov     di, [bx].room.ItemList[ItemCnt]
                call    LongDesc

ItemCnt         =       ItemCnt+2
                endm


                pop     di
                pop     bx
                pop     es
                ret
Describe        endp


; Here is the main program, that actually plays the game.

Main            proc
                mov     ax, dseg
                mov     ds, ax
                mov     es, ax
                meminit

                print
                byte    cr,lf,lf,lf,lf,lf
                byte    "Welcome to ",'"MADVENTURE"',cr,lf
                byte    'If you need help, type the command "HELP"'
                byte    cr,lf,0

RoomLoop:       dec     CurScore                ;One point for each move.
                jnz     NotOverYet

; If they made too many moves without dropping anything properly, boot them
; out of the game.

                print
                byte    "WHOA! You lost! You get to join the legions of "
                byte    "the totally lame",cr,lf
                byte    'who have failed at "MADVENTURE"',cr,lf,0
                jmp     Quit

; Okay, tell 'em where they are and get a new command from them.

NotOverYet:     putcr
                call    Describe
                print
                byte    cr,lf
                byte    "Command: ",0
                lesi    InputLine
                gets
                strupr                  ;Ignore case by converting to U.C.

; Okay, process the command. Note that we don't actually check to see
; if there is a properly formed sentence. Instead, we just look to see
; if any important keywords are on the line. If they are, the pattern
; matching routines load the appropriate values into the noun and verb
; variables (nouns: north=1, south=2, east=3, west=4, lime=5, beer=6,
; card=7, sign=8, program=9, homework=10, money=11, form=12, coupon=13;
; verbs: go=1, get=2, drop=3, inventory=4, quit=5, help=6).
;
; This code uses the noun and verb variables as indexes into a two
; dimensional array whose elements contain the address of the code
; to process the given command. If a given command does not make
; any sense (e.g., "go coupon") the entry in the table points at the
; bad command code.

                mov     Noun, 0
                mov     Verb, 0
                mov     NounPtr, 0

                ldxi    VerbPat
                xor     cx, cx
                match

                lesi    InputLine
                ldxi    NounPat
                xor     cx, cx
                match

; Okay, index into the command table and jump to the appropriate
; handler. Note that we will cheat and use a 14x8 array. There
; are really only seven verbs, not eight. But using eight makes
; things easier since it is easier to multiply by eight than seven.

                mov     si, CurRoom     ;The commands expect this here.

                mov     bx, Noun
                shl     bx, 3           ;Multiply by eight.
                add     bx, Verb
                shl     bx, 1           ;Multiply by two - word table.
                jmp     cseg:jmptbl[bx]

; The following table contains the noun x verb cross product.
; The verb values (in each row) are the following:
;
;       NONE    GO      GET      DROP   INVNTRY QUIT    HELP    unused
;        0       1       2        3      4       5       6       7
;
; There is one row for each noun (plus row zero, corresponding to no
; noun found on line).

jmptbl          word    Bad             ;No noun, no verb
                word    Bad             ;No noun, GO
                word    Bad             ;No noun, GET
                word    Bad             ;No noun, DROP
                word    DoInventory     ;No noun, INVENTORY
                word    QuitGame        ;No noun, QUIT
                word    DoHelp          ;No noun, HELP
                word    Bad             ;N/A

NorthCmds       word    Bad, GoNorth, Bad, Bad, Bad, Bad, Bad, Bad
SouthCmds       word    Bad, GoSouth, Bad, Bad, Bad, Bad, Bad, Bad
EastCmds        word    Bad, GoEast, Bad, Bad, Bad, Bad, Bad, Bad
WestCmds        word    Bad, GoWest, Bad, Bad, Bad, Bad, Bad, Bad
LimeCmds        word    Bad, Bad, GetItem, DropItem, Bad, Bad, Bad, Bad
BeerCmds        word    Bad, Bad, GetItem, DropItem, Bad, Bad, Bad, Bad
CardCmds        word    Bad, Bad, GetItem, DropItem, Bad, Bad, Bad, Bad
SignCmds        word    Bad, Bad, GetItem, DropItem, Bad, Bad, Bad, Bad
ProgramCmds     word    Bad, Bad, GetItem, DropItem, Bad, Bad, Bad, Bad
HomeworkCmds    word    Bad, Bad, GetItem, DropItem, Bad, Bad, Bad, Bad
MoneyCmds       word    Bad, Bad, GetItem, DropItem, Bad, Bad, Bad, Bad
FormCmds        word    Bad, Bad, GetItem, DropItem, Bad, Bad, Bad, Bad
CouponCmds      word    Bad, Bad, GetItem, DropItem, Bad, Bad, Bad, Bad

; If the user enters a command we don't know how to process, print an
; appropriate error message down here.

Bad:            printf
                byte    "I'm sorry, I don't understand how to '%s'\n",0
                dword   InputLine
                jmp     NotOverYet


; Handle the movement commands here.
; Movements are easy, all we've got to do is fetch the NORTH, SOUTH,
; EAST, or WEST pointer from the current room's data structure and
; set the current room to that address. The only catch is that some
; moves are not legal. Such moves have a NULL (zero) in the direction
; field. A quick check for this case handles illegal moves.

GoNorth:        mov     si, [si].room.North
                jmp     MoveMe

GoSouth:        mov     si, [si].room.South
                jmp     MoveMe

GoEast:         mov     si, [si].room.East
                jmp     MoveMe

GoWest:         mov     si, [si].room.West
MoveMe:         test    si, si                  ;See if move allowed.
                jnz     SetCurRoom
                printf
                byte    "Sorry, you cannot go in this direction."
                byte    cr, lf, 0
                jmp     RoomLoop

SetCurRoom:     mov     CurRoom, si             ;Move to new room.
                jmp     RoomLoop

; Handle the GetItem command down here. At this time the user
; has entered GET and some noun that the player can pick up.
; First, we will make sure that item is in this room.
; Then we will check to make sure that picking up this object
; won't overload the player. If these two conditions are met,
; we'll transfer the object from the room to the player.

GetItem:        mov     bx, NounPtr             ;Ptr to item user wants.
                mov     si, CurRoom
                lea     di, [si].room.ItemList  ;Ptr to item list in di.
                call    CheckPresence           ;See if in room.
                jc      GotTheItem
                printf
                byte    "Sorry, that item is not available here."
                byte    cr, lf, 0
                jmp     RoomLoop

; Okay, see if picking up this object will overload the player.

GotTheItem:     mov     ax, [bx].Item.Weight
                add     ax, CurWeight
                cmp     ax, MaxWeight
                jbe     WeightOkay
                printf
                byte    "Sorry, you are already carrying too many items "
                byte    "to safely carry\nthat object\n",0
                jmp     RoomLoop

; Okay, everything's cool, transfer the object from the room to the user.

WeightOkay:     mov     CurWeight, ax           ;Save new weight.
                call    RemoveItem              ;Remove item from room.
                lea     di, ItemsOnHand         ;Ptr to player's list.
                call    InsertItem
                jmp     RoomLoop


; Handle dropped objects down here.

DropItem:       lea     di, ItemsOnHand         ;See if the user has
                mov     bx, NounPtr             ; this item on hand.
                call    CheckPresence
                jc      CanDropIt1
                printf
                byte    "You are not currently holding that item\n",0
                jmp     RoomLoop

; Okay, let's see if this is the magic room where this item is
; supposed to be dropped. If so, award the user some points for
; properly figuring this out.

CanDropIt1:     mov     ax, [bx].item.key
                cmp     ax, CurRoom
                jne     JustDropIt

; Okay, success! Print the winning message for this object.

                mov     di, [bx].item.WinDesc
                puts
                putcr

; Award the user some points.

                mov     ax, [bx].item.value
                add     CurScore, ax

; Since the user dropped it, they can carry more things now.

                mov     ax, [bx].item.Weight
                sub     CurWeight, ax

; Okay, take this from the user's list.

                lea     di, ItemsOnHand
                call    RemoveItem

; Keep track of how may objects the user has successfully dropped.
; When this counter hits zero, the game is over.

                dec     TotalCounter
                jnz     RoomLoop

                printf
                byte    "Well, you've found where everything goes "
                byte    "and your score is %d.\n"
                byte    "You might want to play again and see if "
                byte    "you can get a better score.\n",0
                dword   CurScore
                jmp     Quit

; If this isn't the room where this object belongs, just drop the thing
; off. If this object won't fit in this room, ignore the drop command.

JustDropIt:     mov     di, CurRoom
                lea     di, [di].room.ItemList
                call    InsertItem
                jc      DroppedItem
                printf
                byte    "There is insufficient room to leave "
                byte    "that item here.\n",0
                jmp     RoomLoop

; If they can drop it, do so. Don't forget we've just unburdened the
; user so we need to deduct the weight of this object from what the
; user is currently carrying.

DroppedItem:    lea     di, ItemsOnHand
                call    RemoveItem
                mov     ax, [bx].item.Weight
                sub     CurWeight, ax
                jmp     RoomLoop

; If the user enters the INVENTORY command, print out the objects on hand

DoInventory:    printf
                byte    "You currently have the following items in your "
                byte    "possession:",cr,lf,0
                mov     di, ItemsOnHand[0]
                call    ShortDesc
                mov     di, ItemsOnHand[2]
                call    ShortDesc
                mov     di, ItemsOnHand[4]
                call    ShortDesc
                mov     di, ItemsOnHand[6]
                call    ShortDesc
                printf
                byte    "\nCurrent score: %d\n"
                byte    "Carrying ability: %d/4\n\n",0
                dword   CurScore,CurWeight
                inc     CurScore                ;This command is free.
                jmp     RoomLoop


; If the user requests help, provide it here.

DoHelp:         printf
                byte    "List of commands:",cr,lf,lf
                byte    "GO {NORTH, EAST, WEST, SOUTH}",cr,lf
                byte    "{GET, DROP} {LIME, BEER, CARD, SIGN, PROGRAM, "
                byte    "HOMEWORK, MONEY, FORM, COUPON}",cr,lf
                byte    "SHOW INVENTORY",cr,lf
                byte    "QUIT GAME",cr,lf
                byte    "HELP ME",cr,lf,lf
                byte    "Each command costs you one point.",cr,lf
                byte    "You accumulate points by picking up objects and "
                byte    "dropping them in their",cr,lf
                byte    " appropriate locations.",cr,lf
                byte    "If you drop an item in its proper location, it "
                byte    "disappears from the game.",cr,lf
                byte    "The game is over if your score drops to zero or "
                byte    "you properly place",cr,lf
                byte    " all items.",cr,lf
                byte    0
                jmp     RoomLoop


; If they quit prematurely, let 'em know what a wimp they are!

QuitGame:       printf
                byte    "So long, your score is %d and there are "
                byte    "still %d objects unplaced\n",0
                dword   CurScore, TotalCounter

Quit:           ExitPgm                 ;DOS macro to quit program.
Main            endp
cseg            ends

sseg            segment para stack 'stack'
stk             db      1024 dup ("stack ")
sseg            ends

zzzzzzseg       segment para public 'zzzzzz'
LastBytes       db      16 dup (?)
zzzzzzseg       ends
                end     Main

Chapter Sixteen (Part 11)

Table of Content

Chapter Seventeen  

Chapter Sixteen: Pattern Matching (Part 12)
29 SEP 1996