Sorry your browser is not supported!

You are using an outdated browser that does not support modern web technologies, in order to use this site please update to a new browser.

Browsers supported include Chrome, FireFox, Safari, Opera, Internet Explorer 10+ or Microsoft Edge.

Newcomers DBPro Corner / Create Zork - Tutorial Part I

Author
Message
pluto
16
Years of Service
User Offline
Joined: 18th May 2008
Location:
Posted: 21st May 2008 05:04 Edited at: 25th May 2008 20:14
Zork Tutorial - Part One

Creating the Map and Moving Around

Zork is perhaps the most famous of all text adventures. It has been the gold standard of the genre and highly regarded. I spent countless hours playing this game and many more trying to figure out how to program it. In this tutorial I am going to show you how to make your own zork game!

We are going to recreate a section of the original Zork, which will include the house and the areas just outside of the house.
You will learn how to handle objects, inventory, containers, and parse the commands. So, let's get started!

The first step is to make the map, that is the locations in the game.

In the original Zork the player started out at 'West of House', so this is where we will start. Go to the link below and you will see a map of zork. This is the reference we will use to create our map.

http://infocom.elsewhere.org/gallery/zork1_invisiclues/map-2-3.jpg

We are going to make seven of those locations.

If you look at the zork map you can see where those locations are at and get an idea of how they are situated.

-Our first line of code will make an array with space for seven location names:

DIM LOCATION$(7)

-Next we read in those names into an array:

FOR I = 1 TO 7
READ LOCATION$(I)
NEXT I

DATA "West of House"
DATA "North of House"
DATA "Behind House"
DATA "South of House"
DATA "Kitchen"
DATA "Living Room"
DATA "Attic"


The array above has assigned a number for each location. 1 = "west of house", 2 = "North of House" etc.

We know the player will start out at the "west of house" location which is location number 1.

-So we need to make a variable that represents the player's location and assign it the number 1.


PLR_LOC = 1: REM 1 Represents 'WEST OF HOUSE'


Now, if we were to print LOCATION$(PLR_LOC), we would see 'west of house'. To verify this just run the code we have written so far:



Change the PLR_LOC variable to any number between 1 and 7 and rerun the program and you will see the location has changed accordingly.

Ok, we have our locations but how to get the player movement? First we have to map out which way the player is allowed to move. For instance, the player cannot go east if he is at the 'west of house' location. How does the program know this? Simple, you tell it!

One thing to remember about Zork, the original designers were not consistent with their directions. In the real world, if you take five steps due north, you can take five steps due south to get back to where you started from. This is not so in Zork. In many cases the player can go north but cannot go south to get back! It is strange, but that's the land of Zork.

We will now define 6 directions of travel for each location on the map. The six directions are, north, south, east, west, up and down.

Let us start with north.

We create a north array that will have a north number for each of the 7 map locations. This north number tells us if the player is allowed to move north from that location.

DIM NORTH(7)

FOR I = 1 TO 7
READ NORTH(I)
NEXT I

DATA 2,0,2,0,0,0,0


So, if the player is at location 1, we can look up NORTH(1).
If NORTH(1) = 2, then that means the player is allowed to move north and the player's new location is 2, which happens to be 'North of House'

NORTH(1) can equal any of the 7 map locations. It's just a number that tells us what the player's new location is that he just moved to. If we assign a ZERO to NORTH(1) then the zero tells us that the player is not allowed to go north while located at 'west of house'.

So let's see how this would work.

The player is at location 1 which is 'west of house'. He decides to go north. Now we look at the NORTH array to see if he is allowed.

What is NORTH(PLR_LOC) equal to? Well, the PLR_LOC variable is equal to 1, and that is the player's location. So, NORTH(PLR_LOC) is the same as NORTH(1). NORTH(1) is equal to what? What number did we assign NORTH(1) in our array?

We assigned it the number 2. So, NORTH(1) is equal to 2. That means the player is allowed to move north and we assign the player's new location as 2. Here is the code that does that:

PLR_LOC = NORTH(PLR_LOC)

Now that we know how to define the map movement, let us add the rest of the arrays for the other directions.



The next step is to put this movement into our main loop. Before we enter the main game loop we should display the player's position on the screen:

PRINT LOCATION$(PLR_LOC)

Now, let's code the main game loop.



The first step inside of the main loop is an INPUT statement that gets the player's instructions.

The next step calls a subroutine to parse those instructions, and do whatever the instructions tell us.

These two steps are the core of the game engine. Since we are working with map and movement in this part of the tutorial, the commands will be kept simple. I will cover the parsing, more complicated command sentences in a later part of the tutorial. For right now, the commands we will accept are 'n' - north, 's' - south, 'e' - east, 'w' - west, 'u' - up, 'd' - down, and 'l' - look.

Here is the PARSE subroutine. Look at the first condition, which is 'n'. If the player enters the letter n, the IF statement checks the NORTH(PLR_LOC) variable. If it does NOT equal 0 then the player is allowed to move north and we assign the new location number to the PLR_LOC variable and we set the PLR_MOVE variable to 1 which tells us that the player has moved. If the player cannot move north then the PLR_MOVE variable does not get changed and remains equal to 0. Since PLR_MOVE = 0 we will return a message telling the player he cannot go that direction.




So, now you know how to code the map locations, and the player movement. I have included the full code for this part of the tutorial. In this code you will notice that I have added location descriptions and a screen reset counter inside of the main loop which clears the screen and brings the cursor back to
the top of the screen after a few command entries. This is not needed but helps keep the screen from getting to cluttered.

Remember, the best way to learn is to play with this code. Change some of the north numbers or other directional numbers. You can make your own paths for the player if you choose to. Currently, this code follows the same movement as the original Zork I game. In the next tutorial I will show you how to create objects and player inventory.

Full Code for Part I:



UPDATE I

There is a more convenient method of programming the map locations and directions. It would be nice if we could group all the location data together. As IanM mentioned, it makes for less arrays and changing or adding locations is more convenient. This is not easily done in older BASIC languages, but as it turns out there is a nice way to do this in DBPro. You use typed arrays. Basically, what you will do is create your own type. The good thing about this is that your type definition can be made up of several variables.

So here is what our type definition might look like:

type Loc
loc_name as string
north as integer
south as integer
east as integer
west as integer
up as integer
down as integer
endtype


The type we created is named Loc, and we can make an array of Locs, just like you would make an array of integers or strings.

dim loc_array(7) as Loc

Next we read in all the grouped data:

for i = 1 to 7
read loc_array(i).loc_name
read loc_array(i).north
read loc_array(i).south
read loc_array(i).east
read loc_array(i).west
read loc_array(i).up
read loc_array(i).down
next i

data "West of House", 2,3,0,0,0,0
data "North of House", 0,2,0,7,0,0
data "East of House", 0,0,3,0,0,0
data "South of House", 2,2,0,0,0,0
data "Kitchen", 0,0,0,4,0,0
data "Trophy Room", 0,1,0,0,0,0
data "Attic", 0,0,0,0,0,5


Let's look at the first location of the data:

data "West of House", 2,3,0,0,0,0

This is location #1 so if we were to print the location name:

PRINT loc_array(1).loc_name

It would print 'West of House'

If we were to print the north number we would code:

PRINT loc_array(i).north

This would display the #2

Again, let's look at the data statement:

data "West of House", 2,3,0,0,0,0

Those numbers following 'West of House' represent north, south, east, west, up, and down.

Thus:

2,3,0,0,0,0 is north=2, south=3, east=0, west=0, up=0, down=0

So, at the 'west of house' location if the player goes north he will move to location #2. If player goes south he will move to location #3. The player cannot go east, west, up or down because those directions are equal to 0.

Let's look at that data one more time. Each location has the location name and the directional numbers.

data "West of House", 2,3,0,0,0,0
data "North of House", 0,2,0,7,0,0
data "East of House", 0,0,3,0,0,0
data "South of House", 2,2,0,0,0,0
data "Kitchen", 0,0,0,4,0,0
data "Trophy Room", 0,1,0,0,0,0
data "Attic", 0,0,0,0,0,5


Now, all our location data is in one place so we don't have to scroll through too many arrays to make any changes. Here is a small code snippet that you can run and it prints the location from the typed array we just made. I will change the program code by replacing the simple arrays we used in the beginning with this typed array.




Of course, there are even more variations of this as well and I'm not going to go into everyone of them here. If you have trouble with this then I suggest you stick with the simple array in the original tutorial until you get more comfortable with arrays in general.

Another thing to mention is the number of locations. We know it is 7 but what if we add another location? Then we have to find every array and change the number. Instead of doing that, create a constant variable such as MAX_LOC = 7, and change array(7) to array(MAX_LOC). Now, anytime you want to add a location you only have to change the MAX_LOC constant one time.

I will have more updates to this part I soon. I will add some more shine to it. Then we will move on to part II.

FINAL UPDATE TO PART I

You may have noticed while moving around in the game that when you try to go a direction that is off limits our program displays the message 'You can't go that way." This is fine but you can make it more interesting if you tell the player why he can't go that way. One message might be, 'The bridge is out.' In Zork you will find that the game is sprinkled with such messages, so we will include those messages in our program.

If you'll recall, we used an array to look up the directional number. If it was equal to 0 then that told us that direction was off limits. If it was a positive number then the player's location would change to that number, a new location. We can expand on this idea and use negative numbers to represent different messages telling the player he can't go that way. For instance:

0 = 'You can't go that way.'
-1 = 'The door is nailed shut.'
-2 = 'The gate is locked'

etc...

Zero is still the generic message of 'You can't go that way.'

So, our array data may look something like this:

data 0,-2,2,0,3,1,4

or a typed array could be:

data "North of House", 0,-2,3,1,0,0

Any number 0 or less(negative numbers) means the player is not allowed to go that direction and the number tells us what message to use. Any positive number means the player can move that direction and his position (PLR_LOC) changes to that positive number.

Now that you know the idea behind this, let us put it into action.

First let us declare an array for our messages:

REM PLAYER CAN'T GO THAT WAY MESSAGES
MAX_NOGO = 3
DIM NOGO$(MAX_NOGO)
FOR I = 0 TO MAX_NOGO
READ NOGO$(I)
NEXT I

DATA "You can't go that way."
DATA "The door is boarded and you can't remove the boards."
DATA "The windows are all boarded."
DATA "The door is nailed shut."


The messages are the same as Zork I, around the house area.

Notice the messages will be represented in numeric order like this:

NOGO$(0) - "You can't go that way."
NOGO$(1) - "The door is boarded and you can't remove the boards."
NOGO$(2) - "The windows are all boarded."
NOGO$(3) - "The door is nailed shut."


These are our 4 messages.

We use the number 0 and negative numbers to identify them.

0, -1, -2, -3

We have to change these to positive numbers after we get them so that we can match them up to the NOGO$ messages.

For instance:

Change -3 to 3 so that we can put it in the NOGO$ array like this:

PRINT NOGO$(3)

This would display:

'The door is nailed shut.'

What we can do is use the ABS function which gives us the absolute value of the number and then match that up with our message array. So, ABS(-3) is the same as 3.

Now that we have a postive number we can use it on our message array to print the matching message.

First we use the ABS function:

So, if NORTH(1) = -3

MESSAGE_INDEX = ABS( NORTH(1) )

Same as:

MESSAGE_INDEX = 3

Now, we can use it as our index for the message array NOGO$

PRINT NOGO$( MESSAGE_INDEX )

Is the same as:

PRINT NOGO$( 3 )

Remember what message NOGO$(3) is? Let's list them:

NOGO$(0) - "You can't go that way."
NOGO$(1) - "The door is boarded and you can't remove the boards."
NOGO$(2) - "The windows are all boarded."
NOGO$(3) - "The door is nailed shut."


This concludes this part of the tutorial, as I will not make any new updates for this first part. If you are having trouble with this part of it don't worry, you can always just use the standard message in the original program. Of course you may think of other ways to make them yourself if you choose.

I have added the final program, complete for PART I of the tutorial. It includes the message codes and I have replaced the simple location arrays with typed arrays. I have included lots of comments to help make it clear. You will notice that the code has not changed that much, so you know most of it already and the rest of it is pretty simple.

IanM
Retired Moderator
22
Years of Service
User Offline
Joined: 11th Sep 2002
Location: In my moon base
Posted: 21st May 2008 20:14
I'm always a little concerned when someone new to the forums starts posting tutorials, but this isn't too bad. Where it falls down a little is that it appears to have been written for a more limited version of BASIC than DBPro.

- You have multiple arrays for a room rather than storing everything for each room in a typed array.
- You have configurable data in data statements rather than loading from a file.
- You have configurable data in code statements (room descriptions) rather than a file.
- Your data is broken up in ways that aren't easy to maintain (all north, then all south etc) when storing everything for a single room together would be easier.

So, not bad, but not really the way you should doing it to teach others how to write a text adventure in DBPro.

Must Program
17
Years of Service
User Offline
Joined: 22nd Nov 2007
Location:
Posted: 21st May 2008 21:04
@IanM you said that it should have been shown differently well i looked at a tut for parser games where it was shown the way u just eplained and i read it 3 times and i had no idea what i was doing and the only thing i learnt from it was the word parser. From plutos i was able to easily understand it and i can now make a good running working parser game so to me the way he has done it is better. i completely understood it and learnt alot.

All the people who know nothing about coding parser games need a simple tut to explain it to them and this is what we need. The way you want it is too complicated and very hard to understand for you it is easier because you know quite abit about coding but im a new basic begginer and i need just the basics and this is what it is this is what i need and i learnt more from this one post than i have from all the other parser tuts put together so thankyou pluto and sorry mr mod for having a go at you but it had to be said and i appologise again for the lengthy post

@pluto congrats its a great tut keep up the good work buddy i learnt loads from this tut i cant wait for the next one
adding items yay i like items i do

Working on The Hunt and 7 Stages both are text based games want a demo of either then just ask.
IanM
Retired Moderator
22
Years of Service
User Offline
Joined: 11th Sep 2002
Location: In my moon base
Posted: 21st May 2008 21:19
First, I made no comments on the parser - that's fine as it is right now.

Second, every time you need to change a room description, you need to recompile the code. Every time you need to add a new room, you need to recompile the code.

Consider adding a new room to that code:
- 7 arrays need to be changed.
- 7 data statements need to be changed.
- New code needed to display the room description.

Or:
- Add the room and directions to a text file.

Which is better? Which is the better thing to teach?

Also, if you are learning DBPro from the ground up from this, you are starting in the wrong place. There are plenty of tutorials that cover the basics. If you know the basics and want to know how to write a game, then a tutorial about that game type is the place to continue from.

pluto
16
Years of Service
User Offline
Joined: 18th May 2008
Location:
Posted: 22nd May 2008 02:21
Hello IanM,

I understand your concerns, but I think you are being a bit unfair.

First of all, I mentioned in the tutorial that there were other ways to program this type of game. I am well aware of the number of arrays and also the use of files. I have been programming in several languages, including C++ for many years. I have worked with classes, inheritence, structures, pointers, and the list goes on and on. I know what I'm doing.

Your point that it may not be the most efficient method is well taken and I accept that criticism. However, I have found through experience, that beginners can follow the code much better if it is broken down like this. Once they grasp the concepts then it's easier to move on from there. So, I made this as simplistic as possible for that reason.

I strongly disagree when you say that it is not how you should teach this. On the contrary, there is nothing wrong with the code, it is technically sound and works fine. You may not approve of the particular method, but that's ok. I never said it was the only method nor the best nor the most efficient. That was not my intent. I went with what I personally thought would be the easiest to grasp.

So, I hope you'll overlook the shortcomings and accept my tutorial for what it is. There will be opportunities in the future to show more efficient methods down the road and the student will be able to appreciate them even more.
pluto
16
Years of Service
User Offline
Joined: 18th May 2008
Location:
Posted: 22nd May 2008 05:28 Edited at: 25th May 2008 01:44
Ok, I have updated the tutorial with a section on the typed array method for locations. I am not going to talk about files until some of the later tutorials.
pluto
16
Years of Service
User Offline
Joined: 18th May 2008
Location:
Posted: 23rd May 2008 03:14
I just posted the final updates for PART I. If you have any questions or need help just let me know. I will be working on PART II - Items and Inventory pretty soon.
pluto
16
Years of Service
User Offline
Joined: 18th May 2008
Location:
Posted: 24th May 2008 21:50 Edited at: 25th May 2008 19:51
Zork Tutorial - Part II - OBJECTS AND INVENTORY

In the first tutorial we created the game world with it's locations, movement and a simple command parser.

Now we will learn how to create objects and place them in the game. We will also make the player's inventory.

For now, we will only make 2 objects, the mailbox and the leaflet located at the 'west of house' location. We will add the rest at a later time.

Objects, like a mailbox have attributes, information about them. For instance, the location of the object and the name of the object are all attributes. There are many attributes that an object can have. Some objects can be opened like the mailbox while others cannot. Some objects can be taken by the player, while others cannot. We need to give each object certain attributes that we will include in the game.

For now, we will start with just 2 attributes and add others later. These two attributes are the object's name and it's location.

First we tell the program how many objects there are in the game.
let's make a variable called OBJ_MAX. This is equal to the number of objects in the game, which we will set to 2 for now.

OBJ_MAX = 2

Next, We will make an array type for the objects.

First we create the array definition:

REM CREATE A TYPE DEFINITION FOR OBJECTS
type OBJ
NAME as string
LOCATION as integer
endtype


So, we created a new type and called it OBJ. It has two variables, NAME and LOCATION. Those are the object attributes.
Next we create an array that will contain each object and it's attributes.

DIM OBJ_ARRAY(MAX_OBJ) as OBJ

Now, let's read in the attribute information into the array:

REM POPULATE THE ARRAY WITH OBJECT DATA
FOR I = 1 to MAX_OBJ
READ OBJ_ARRAY(I).NAME
READ OBJ_ARRAY(I).LOCATION
NEXT I

DATA "small mailbox", 1
DATA "leaflet", 2


As you can see, it is fairly simple. There is the name of the object followed by it's location.

So, object #1 is the 'small mailbox' and it is located at location #1 which is the starting position 'west of house'

You will notice that I placed the leaflet at a different location, #2 which is 'north of house'

To display each object we would code a print statement with the array name. For instance, to display the name of the first object, the 'small mailbox' we would code this:

PRINT OBJ_ARRAY(1).NAME

To display the location number of the leaflet we would code:

PRINT OBJ_ARRAY(2).LOCATION

Let's try it. Here is the code to print object information (attributes). Go ahead and run this to see it in action:



Now that we have the objects defined we know how to display them we will go ahead and put that code in our game.
We will display the name of the object right after the location and the description. So it will look something like this:

West of House
You are standing in an open field west of a white house, with a boarded front door.

There is a small mailbox here.


To display the objects at the player's location we need to search through our object list to see if the object's location number is the same as the player's location number. We will create a for-next loop which will look at each object one at a time starting with object #1.

If the location of the object = location of player we will display the name of the object.

Thus:

IF OBJ_ARRAY(1).LOCATION = PLR_LOC

Next, print the name of the object:

PRINT OBJ_ARRAY(1).NAME


Instead of just displaying the name of the object we can spice it up a bit. For instance, in Zork I, it usually says:

'There is a small mailbox here.'

So, we will add those words too:

PRINT "There is a " + OBJ_ARRAY(I).NAME + " here."

Now, here is the FOR-NEXT loop that checks each object and display's it if it is at the player's location:

REM LIST ANY OBJECTS AT PLAYER LOCATION
FOR I = 1 TO MAX_OBJ

IF OBJ_ARRAY(I).LOCATION = PLR_LOC

PRINT "There is a " + OBJ_ARRAY(I).NAME + " here."

ENDIF

NEXT I


I have added all the above code into the game. Take a look at it and you will see it is a simple addition.
Run this code and you will see the mailbox at the starting location. If you move north, you will see the leaflet.
We will discuss player inventory in the next update. Have fun.




UPDATE I

INVENTORY

As you learned earlier, objects have attributes such as what their name is and where they are located. We will add another attribute to our objects. This attribute let's us know if the object is with the player. The number 0 tells us that the object is not with the player. The number 1 tells us that the player has the object in his inventory. So, let's add that to the object array that we created earlier.

Here is the original object type:

type OBJ
NAME as string
LOCATION as integer
endtype


Now we will add the third attribute and call it INVENTORY.

Add it to the OBJ type:

type OBJ
NAME as string
LOCATION as integer
INVENTORY as integer
endtype



Then add it here where we read in the object data:

for i = 1 to MAX_OBJ
read OBJ_ARRAY(i).NAME
read OBJ_ARRAY(i).LOCATION
read OBJ_ARRAY(i).INVENTORY
next i


Now we need to add either a 1 or a 0 for each object, a 0 if object is not with player and a 1 if object is.
For the mailbox we will assign it a 0 and the leaflet a 1, so the player will be carrying the leaflet.

Here is the original object data, with NAME and LOCATION attribute.

REM NAME, LOCATION
data "small mailbox", 1
data "leaflet", 0


Now, here it is after we add the third attribute of INVENTORY.

REM NAME, LOCATION, INVENTORY
data "small mailbox", 1, 0
data "leaflet", 0, 1


And here is the complete object array that we created:


REM DECLARE MAXIMUM NUMBER OF OBJECTS IN GAME
MAX_OBJ = 2

REM CREATE A TYPE DEFINITION FOR OBJECTS
type OBJ
NAME as string
LOCATION as integer
INVENTORY as integer
endtype

REM CREATE AN ARRAY OF OBJECTS
dim OBJ_ARRAY(MAX_OBJ) as OBJ

REM POPULATE THE ARRAY WITH OBJECT DATA
for i = 1 to MAX_OBJ
read OBJ_ARRAY(i).NAME
read OBJ_ARRAY(i).LOCATION
read OBJ_ARRAY(i).INVENTORY
next i

REM NAME, LOCATION, INVENTORY
data "small mailbox", 1, 0
data "leaflet", 0, 1


We still only have two objects but now each object has three attributes. You can create any attribute like this, it's that easy.
For instance, if you wanted to add weight to objects you could make another number which would represent how much the object weighs. You could call it the WEIGHT attribute.

Now that we have added the INVENTORY attribute we will add a new command to the parser. Whenever the player enters the letter
'i' we will display whatever he is carrying in his inventory.

Of course we will check each object and see if it's INVENTORY variable is = 1 and if it is then we list that on the screen as
the player's inventory. So we would code:

IF OBJ_ARRAY(I).INVENTORY = 1

then

PRINT OBJ_ARRAY(I).NAME

If 1 then print object name, pretty simple.

But what if the player is carrying a bunch of objects?

What we need to do is make our trusty FOR-NEXT loop and go through each object one at a time and check each INVENTORY attribute. Like this:



And if the player is not carrying anything? In that case we will reply with the standard Zork reply:

'You are empty-handed.'

To do this we can make a counter variable named INV_CNT. Each time we find an object in the player's inventory we simply add 1 to the INV_CNT.

When we find the very first object that's in the player's inventory we can display the message:

'You are carrying:'

We print that message just once, only starting with the first object. Once we print that message then we print the name of the object. The remaining objects in the inventory will be printed below that, one at a time. So, it would look like this:

You are carrying:
leaflet
sword
bottle


So, let's look at the code:

First check the object to see if it's in player inventory:

IF OBJ_ARRAY(I).INVENTORY = 1

If it is, then increment the INV_CNT (add 1 to INV_CNT)

INC INV_CNT

Next, check to see if this is the first object and display the 'You are carrying:' message. We will know if it's the first object because the INV_CNT will equal 1.

IF INV_CNT = 1 THEN PRINT "You are carrying:"

Next display the name of the object.

PRINT OBJ_ARRAY(I).NAME

After we finish checking every object and we don't find any in the inventory then we display the 'You are empty handed.' message. If the INV_CNT is 0 then there is nothing in inventory, so that's the variable we need to check:

IF INV_CNT = 0
PRINT "You are empty-handed."


Now, we put all of that code together and it looks like this:




Now, I'm going to add a simple TAKE and DROP command to the parser. You will only be able to take and drop the leaflet for now.

In the latter part of the tutorial I will go more indepth on the parser and we will learn how to make a Zork parser, but that
will have to wait till later. Once we have a working Zork parser we will be able to add the full TAKE and DROP commands for all
objects. What you learn here will put to use when we get the full parser going.

So what do we have to change when the player takes the leaflet?

Well, we need to change the leaflet INVENTORY attribute from 0 to 1, because the 1 means the player has the object.
But we also need to remove the object from the ground at wherever the player is located. So we have to change the object's
LOCATION attribute from a 1 to a 0.

The leaflet object is object #2. So we need to code:

OBJ_ARRAY(2).INVENTORY = 1 i.e - in player's inventory now

OBJ_ARRAY(2).LOCATION = 0 i.e. - no longer on ground at location

Before we do that we should check to see if the object is at the player's current location, because if the object is in the
kitchen and the player is outside the house we don't want him to be able to take it! So, we code:

IF OBJ_ARRAY(2).LOCATION = PLR_LOC

The simple parser code would look something like this:




Now, to drop the leaflet you just reverse this and check to see if the player has the object because you don't want him dropping something he doesn't have.

Here is the full code of everything we have covered so far. Run this code and go north and take the leaflet. Now look at your inventory 'i' command and you will see it. Try dropping it somewhere and enter the look command 'l' to see if the leaflet you dropped is there.




We are not finished with objects and managing objects but this is a good start. We have to learn the Zork parser now, and that
will be the subject of the next part of the tutorial.

For now, play with the code, add a new object or even a new location if you like.

Now I leave you with a link to the original Zork I map that was made by the programmers of the game. Check it out!

http://almy.us/image/dungeon.jpg
pluto
16
Years of Service
User Offline
Joined: 18th May 2008
Location:
Posted: 25th May 2008 01:51 Edited at: 25th May 2008 02:23
I just added PART II - OBJECTS AND INVENTORY

I added it to this thread to keep everything together in one place.

I also went back through PART I of the tutorial and added more explanations and examples so that it could be easily understood. I will continue to improve upon it as I go along.
TDK
Retired Moderator
22
Years of Service
User Offline
Joined: 19th Nov 2002
Location: UK
Posted: 25th May 2008 09:54
One tip I would like to offer is to highlight code in bold that is not in code boxes.

It separates what you actually type into the DBP editor (that needs to 'sink in') from the main tutorial text and improves readability no end...

TDK_Man

pluto
16
Years of Service
User Offline
Joined: 18th May 2008
Location:
Posted: 25th May 2008 16:20
Excellent Idea. Thanks for the tip!
IanM
Retired Moderator
22
Years of Service
User Offline
Joined: 11th Sep 2002
Location: In my moon base
Posted: 25th May 2008 16:49
That's much better

I understand why you'd want to keep it simple, but in this case, you aren't teaching them the syntax of the language - there are other tutorials for that - you are teaching them how to write this type of game. Or at least, that's what I understood from your original posting.

Two last small things and I'll leave you to it.
1 - in your types you are using floats for all numeric data. I'd suggest that instead of using a float for a room number (movement data and item locations) you use an integer instead.
2 - your location descriptions are still in code - I'd suggest that you move them into your data and read them into your LOC type as a string.

pluto
16
Years of Service
User Offline
Joined: 18th May 2008
Location:
Posted: 25th May 2008 17:45 Edited at: 25th May 2008 17:45
Hey IanM,

Thanks for the critique. I know there are tutorials for that but I guess the reason I took that approach is because I remember how hard it was for me when I first started. Of course, I was only 10 at the time lol. Anyway, sometimes it's ok to show them the most basic of code but then later improve upon it, especially with the strength of DBpro. That was my line of thinking.

I did use floats but integers are more suitable here. I hadn't noticed because much of my other programs require floats so it almost gets second nature. Thanks.

I will be moving the descriptions with the LOC and then place the arrays in a file before the end of the tutorial.

By the way, I appreciate all the advice and tips, since this is my first tutorial! Just to let everyone know though, I am not new to DarkBasic. I purchased DB Classic when it first came out several years ago. I also got DBPro as soon as it was released. Though I haven't posted until now I do visit the forums.

I will try to improve the tutorial on an ongoing basis, thanks again for all the input.
pluto
16
Years of Service
User Offline
Joined: 18th May 2008
Location:
Posted: 25th May 2008 19:47 Edited at: 25th May 2008 20:02
Update:

I noticed the text size in the game had changed to a much larger font. I updated the tutorial and it is now the normal size.
Jaeg
18
Years of Service
User Offline
Joined: 16th Mar 2006
Location: Indiana
Posted: 26th May 2008 20:54
I like your tutorial. It has tempted me to write a text adventure for the competition when and if the start it.

Thanks.

If you get mad and want to type something nasty about another person do this-Type what you want to say in the box then press ctrl-a and hit delete then type what you should say.
pluto
16
Years of Service
User Offline
Joined: 18th May 2008
Location:
Posted: 27th May 2008 00:00
Thanks Jaeg! I'm glad you like it. I hope they go ahead with the contest, I think it would be great.
pluto
16
Years of Service
User Offline
Joined: 18th May 2008
Location:
Posted: 27th May 2008 00:03 Edited at: 22nd Jun 2008 20:13
Create Zork - Tutorial Part III - PARSER

BASIC SYNTAX OF THE PARSER

Finally, it's time to learn the parser!

Before Zork came along, the first adventure games used simple command systems. Some were menu driven such as 'Hunt the Wumpus', one of the very first adventure games. The game had only three commands the player could choose from, 'Shoot, Move or Quit (S-M-Q)'. Shortly after that, the Colossal Cave Adventure came on the scene with a two word parser that allowed the player to enter a verb followed by a noun, such as 'enter building'. That type of two word parser was commonly used in many text adventure games up until Zork. The Zork parser blew the other games out of the water with it's sophisticated interpreter which allowed players to enter full sentences, often with many commands. With such a robust parser, the player was able to manipulate the game world much more. This allowed him to become more immersed in the game and this is the parser you will learn to make.

A great deal of what the parser does is related to objects. Opening objects, dropping objects, examining objects and the list goes on. The parser is practically made for objects. If we keep this in mind, it will make programming the parser a bit easier. The parser is also used to give the player directives like moving from one location to another or saving the game. Yet, the player can also be viewed as an object if you think about it.

All parser commands start with a verb, such as take, drop and examine, and are usually followed by a noun.

The basic syntax of a simple two word parser is this:

VERB NOUN

VERB = 'DROP'
NOUN = 'STONE'

'DROP STONE'



The Zork parser expanded on that syntax by adding a preposition followed by a second noun. This allows the player to use one object with another. The basic syntax of this parser is the four word construct:

VERB NOUN PREPOSITION NOUN

VERB = 'PUT'
NOUN = 'GOLD'
PREP = 'IN'
NOUN = 'SACK'

'PUT GOLD IN SACK'


You can also label the syntax like this:

ACTION OBJECT PREP OBJECT

This basic construct is at the heart of the Zork parser. This can be expanded further because objects can have descriptions, such as, GOLD COIN'. So the syntax with descritpions are:

ACTION DESC OBJECT PREP DESC OBJECT

'PUT RED CRAYON ON WOODEN DESK'

'PUT THE RED CRAYON ON THE WOODEN DESK'


This basic syntax can be added to further if we join two commands with a conjuction like this:

'PUT CRAYON ON DESK AND TAKE THE PEN FROM THE DRAWER'

The basic syntax is still the same, we just have two commands together:

'PUT CRAYON ON DESK'

'TAKE PEN FROM DRAWER'


We would deal with this as if the player entered two separate commands, one at a time.

Now that you know the basic syntax, we can begin coding the parser in the next section.

SECTION II - CODING THE PARSER

The best way to code the parser is to break everything down into steps. Below is a list of the main steps or tasks that we have to code. I will give a short summary of each step and then we will learn how to code them.

Step 1

Copy each word from the command sentence and put them into an array.

It is much easier to deal with the command if we have the sentence broken down into individual words in an array and we can focus on one word at a time. Let's look at a typical command sentence:

'PUT THE BROWN SACK ON THE TABLE'

When we put each word into an array, it might look like this:

ARRAY(1) = 'PUT'
ARRAY(2) = 'THE'
ARRAY(3) = 'BROWN'
ARRAY(4) = 'SACK'
ARRAY(5) = 'ON'
ARRAY(6) = 'THE'
ARRAY(7) = 'TABLE'


Now, this is much easier to manage when we are trying to process the commands.

When coding the parser it's always a good idea to simplify; cut out anything unnecassary. We want to get the command sentence 'boiled down' to just the bare-bones. One obvious thing we could do is remove all 'the' words. These words tell us nothing, so we can remove them.

Our array now looks like this:

ARRAY(1) = 'PUT'
ARRAY(3) = 'BROWN'
ARRAY(4) = 'SACK'
ARRAY(5) = 'ON'
ARRAY(7) = 'TABLE'


The next thing we can do is find any descriptive words and remove them. The word 'brown' in 'brown sack' can be removed. Now the array is even shorter:

ARRAY(1) = 'PUT'
ARRAY(4) = 'SACK'
ARRAY(5) = 'ON'
ARRAY(7) = 'TABLE'


What we are left with is just the essentials, and it is manageable.

Step 2

Identify word types.

Every word in a command is of a certain type. For instance, the mailbox is an object. That's the type of word it is, an object word. We need to identify each word and the type of word it is. Is it an object? A verb? We will identify the word type with an integer. Here is a list of types we will use:

0 - UNKNOWN TYPE
1 - VERB
2 - OBJECT DESCRIPTION
3 - OBJECT
4 - PREPOSITION
5 - CONJUNCTION


Looking at the previous command, we can see what type each word is:

PUT=1 BROWN=2 SACK=3 ON=4 TABLE=3

The word 'put' is type 1 which is a verb, 'brown' is type 2 which is a description. The word 'on' is a type 4 which is a preposition.
Prepositions are words such as 'on', 'in' and 'from' found in typical commands, such as:

'PUT GOLD IN SACK'

'TAKE GOLD FROM SACK'

Conjunctions join command sentences together. The most common word for this is the word 'and'.

'TAKE SWORD AND GO EAST'

These are two different commands separated by the conjunction 'AND'.

When we come accross a word type which we can't identify then we mark it as unknown and display a message telling the player.

Step 3

Process the words of the command.

This is a fairly simple task. Look at the first word in the array which is the first word of the command. The first word should always be a verb, if it isn't then display an error message and return to main loop. If the first word is a verb then call the subroutine for that verb.

For instance, if the first word is 'TAKE' then you would execute the 'GOSUB TAKE' routine.

These three steps are the basic setup of your parser and we will now look at the code for each step starting with step 1.

Coding Step 1

Copy each word from the command sentence and put them into an array.


Before we can copy each word in the command sentence to an array, we have to create the array first.

REM COMMAND ARRAY
TYPE CA
CWORD AS STRING
CTYPE AS INTEGER
ENDTYPE


The first variable in the array is a string which will store each word from the command sentence. The second variable will store what type of word it is, such as verb, object etc.

Now we are going to make the array a dynamic array. This array will not have a set size because when the player enters his commands we have no way of knowing the number of words in those commands. They will vary from one command sentence to another. For instance, 'TAKE MAP' is a two word command while 'UNLOCK IRON GATE WITH SKELETON KEY' is a six word command.

We will call our array BWORDS().

DIM BWORDS() AS CA

This dynamic array will grow in size as needed depending on the number of words in the command.

Now that we have an array we will make a subroutine that copies each word in the command and plugs them into the array. We will name this subroutine CMD_BATCH, because the array will basically be a batch of command words. So our code to call this routine is:

GOSUB CMD_BATCH

This line of code would go at the end of our PARSE routine.

Now let's look at the actual subroutine itself. The first line gets the character length of the command.


REM CHARACTER LENGTH OF COMMAND
CL = LEN(CMD$)


So, if the command were 'GO EAST', CL would equal 7, seven characters including the space. Why do we need to know the length of the command?

Because it will help us as we try to identify each word in the command sentence. Every word is separated by a space, so whenever we reach a space in the command, we know that the previous letters make up a word.

We will use a loop to go through each character one at a time to see if it is a space. In order for us to look at all of the characters, we have to know how many characters there are total in the command sentence.

In this example, the character length of the command sentence is 7, thus CL is equal to 7. So we know the last character is at character position number 7. This variable lets us know when we have looked at the last character.

Now we need a variable that keeps track of the specific character we are currently looking at. We will look at the very first character starting out, so we make the variable equal to 1.

REM CHARACTER POSITION
CP = 1


As we copy each word into the array we will need to have an array index that increases with every word.

So for every word that we find, the index will increase by 1. We will give it the value of 0 starting out.

BW_INDX = 0

Next we empty the array. This is necessary because the next time the player enters another command we don't won't this array to contain any words from the previous command. So, this empties out the array and lets us start over again for each new command.

EMPTY ARRAY BWORDS()

Now we create the loop, this loop will repeat over and over again until we get to the last letter of the command and have every word copied to the array. We will use the REPEAT-UNTIL loop.

REM PERFORM LOOP UNTIL ENTIRE BATCH IS COMPLETE
REPEAT


The first thing we do inside the loop is look at each character, to see if it is a space.

REM IF WE FIND A SPACE THEN PUT TRAILING WORD(previous letters) INTO BATCH(array)
IF MID$(CMD$, CP) = CHR$(32)


The 'IF MID$(CMD$, CP)' says look at the middle of this command, at the character position equal to CP. Let's say that CP is currently equal to 5. So, this looks at the fifth character in the command sentence. If the command is 'TAKE GOLD' then the fifth character is a space. The '= CHR$(32)' is the ascii representation of a space. So, that command line means:

'if the fifth character = a space'

If it does equal a space then we can say that the previous characters are a word. For instance, in the command 'TAKE GOLD', all the characters before the space make up the word 'TAKE'. That is our word that we want to put into the array.

Before we copy it into the array we need to make room for the word inside of the array. Remember this is a dynamic array. It grows one word at a time and starting out it has no room. So, we tell it to add a new array room, and insert it starting at the bottom or beginning of the array:

ARRAY INSERT AT BOTTOM BWORDS()

Now that we have increased the size of the array by 1, we need to assign it's new size to the array index.

As this loop executes over and over again the array size will grow as each new word is added. The index is the number of the latest array room that has been added and that tells it where to put the next word in the array:

BW_INDX = ARRAY COUNT(BWORDS())

Next we copy all the previous characters that were behind the space that we found. Those characters are the word which we put into the array:

BWORDS(BW_INDX).CWORD = LEFT$(CMD$, CP)

Let's look at that a bit closer. 'LEFT$(CMD$, CP)' is every character to the LEFT of character position CP. So, if the command is 'GO EAST' and CP is at the space character position which is position number 3, then every character before position number 3 is a word. In this case there are two characters before the space, a 'G' and an 'O', so the word is 'GO'. All the command is saying is:

BWORDS(BW_INDX).CWORD = "GO"

The next line of code chops off the previous word from the command, so 'GO EAST' becomes 'EAST'. Here is why we do that. As each word is found and copied to the array we no longer need it in the command. There is also a more important reason. Let's say the command is this:

'TAKE ROPE AND KNIFE'

Remember, each time we find a space, we copy all the previous letters as one word, and plug it into the array. Well, if we get to the second space in this command, all the previous characters are, 'TAKE ROPE'. We only want to copy the word 'ROPE' not 'TAKE ROPE'. If we remove each
word as we copy them, we won't have this problem. For instance:

'TAKE ROPE AND KNIFE'

We copy 'TAKE' then remove it from the command sentence. The command sentence now becomes:

'ROPE AND KNIFE'

And every character that comes before the space is now 'ROPE' and not 'TAKE ROPE'

We remove, leave out each word as we copy them in this way. Here is the command that does that:

CMD$ = RIGHT$(CMD$, (CL-CP))

CL is the length of the command and CP is the character position. What this says is, make the new command sentence = the old command sentence minus every character up to and including the current character position. So, once again let's look at the command:

'TAKE ROPE AND KNIFE'

The character length of this command is 19, so CL = 19. The first space we come to is at character position 5, so CP = 5. CL-CP is 19-5 or 14. So, the 14 characters to the RIGHT of the command (CMD$) is 'ROPE AND KNIFE'. So the new command sentence is 'ROPE AND KNIFE'. We have left out the word 'TAKE'. Basically, we removed that word from the command sentence. Thus:

CMD$ = RIGHT$(CMD$, (CL-CP)) - When we copy the new command sentence from the old command sentence, we leave out the previous word.

So, let's see it again, step by step:

The original command is 'TAKE ROPE AND KNIFE'

This command is 19 characters in length, CL = 19.
The space is at character position 5, CP = 5.

CMD$ = RIGHT$(CMD$, (CL-CP))

Same as:

CMD$ = RIGHT$(CMD$, (19-5))

Same as:

CMD$ = RIGHT$(CMD$, (14))

Same as:

CMD$ = 'ROPE AND KNIFE' (notice its the last 14 characters on the right of the original command)

The word 'TAKE' has been removed.


Each time we leave out a word from the command sentence, it's character length changes, gets smaller. We need to get the new command length:

CL = LEN(CMD$)

Since we are adding another word to the array, we need to increase the array index by 1.

INC BW_INDX

So you can see, the array gets larger as we copy more words to it, while the command sentence gets smaller as we remove words from it. Remove word from command and put it into array, that's how it works.

Once we finish a word copy, we now reset the character position back to 0 and start all over until we find another space with another word. For every space we find, we copy a word and then remove it from the command.

CP = 0

ENDIF

The endif signifies the end of the condition: If we find a space then copy word into array and remove word from command, end condition.

If we still haven't found a space yet, then we increment the character position by 1 and when the loop repeats, it will look at the next character to see if it is a space. So, the process begins again until the last character of the command sentence is looked at.

INC CP

UNTIL CP => CL : REM REPEAT LOOP UNTIL LAST CHARACTER OF COMMAND IS REACHED

After the loop is finished and there is no more spaces, the command sentence will contain one last word. We copy this remaining word into the array:

REM GET LAST WORD
ARRAY INSERT AT BOTTOM BWORDS()
BW_INDX = ARRAY COUNT(BWORDS())
BWORDS(BW_INDX).CWORD = CMD$


And that's it. That is the process for finding the words in the command sentence and putting them into the array.

Here is the code for that subroutine.




You will notice that it is not that long really, and fairly easy to follow.

Now, here is the entire program with the new code:



I have added a bit of code that will print out each word in the array so that you can see for yourself how it works. For instance, if you were to type in:

'TAKE THE KEG OF BEER FROM TROLL'

You will see the array print out like this:

WORD 0 = TAKE
WORD 1 = THE
WORD 2 = KEG
WORD 3 = OF
WORD 4 = BEER
WORD 5 = FROM
WORD 6 = TROLL

Go ahead, give it a try and you will see the parser has copied each word into the array in the code we just made.

In the next section we will move on to Step 2, identifying the word types.

Beyond Zork, takes Zork to the next level. Check it out:

http://gallery.guetech.org/beyond/beyond.html

Coding Step 2

Identify Word Types

Now that we have all the command words in an array, the next step is to identify their types. Identifying what type each word is will help us when we process each command. You will see how this works later.

We will begin with objects. Any word which is an object type will be assigned the number 3.

Let's create a new subroutine named WTYPE, which will identfy word types.


REM ASSIGN TYPES TO WORDS
WTYPE:


Now we identify object types. Remember, we have put all the command words into an array and we have all objects in an array too. What we will do is walk through each command word and compare it to every word in the object array. If the word matches any of those object words then we assign it as an object word type. So the method will be along these lines:

If first command word = object 1 then type of word = 3
If first command word = object 2 then type of word = 3
If first command word = object 3 then type of word = 3


If second command word = object 1 then type of word = 3
If second command word = object 2 then type of word = 3
If second command word = object 3 then type of word = 3

...


Of course, that's not the actual code but pseudo-code that shows you what the routine will do. In this case, we will use two loops. The main loop will pull each command word in the command word array. The second loop will go through each object in the object array.

So, just like the pseudo-code above, it will look at the first command word and compare it to all of the objects in the object array. Then, it will pull the second command word and compare it to all the objects in the object array. It will do this until every command word has been compared and it's type identified.

First, we need to set the index to 0 for the command word array.

WT_INDX = 0

Then we enter the main loop which is a Repeat-Until loop:

REPEAT

Now make a For-Next loop to compare the command word to every object in the object array:

REM CHECK ALL OBJECTS AND DESCRIPTIONS TO SEE IF ANY MATCH COMMAND WORD
FOR I = 1 to MAX_OBJ


REM IF COMMAND WORD MATCHES AN OBJECT
IF BWORDS(WT_INDX).CWORD = OBJ_ARRAY(I).NAME


If the command word matches the object, assign it type number 3.

REM ASSIGN TYPE TO ARRAY
BWORDS(WT_INDX).CTYPE = 3


End the condition and go through the loop for each word comparing them to all objects.

ENDIF

NEXT I


Now add 1 to the command word index, which will point to the next command word.

INC WT_INDX

Do the loop again with the next command word until all command words are processed.

UNTIL WT_INDX > ARRAY COUNT(BWORDS())

Here's the entire routine:



We have now assigned object types, but we also need to assign the rest of the types too. Before we do that though, we need to include something else. If we identify a word as an object type, it would help if we could also find out what the object is. Is it a sword, a torch, etc. What we can do is create a new variable in the command array and assign the object number to it. Let's look at the command array as it is before we add the object number variable:

REM BATCH COMMAND ARRAY
TYPE BCA
CWORD AS STRING
CTYPE AS INTEGER
ENDTYPE


You can see there is one variable to store the command word and one to store the type. Now we add another to store the object number:

REM BATCH COMMAND ARRAY
TYPE BCA
CWORD AS STRING
CTYPE AS INTEGER
CNUM AS INTEGER
ENDTYPE


We named this variable CNUM. Since this variable stores the number of the object, all other words will not use this variable. So, if the word is a verb for instance, the CNUM variable will remain empty. It will only be assigned a number if the type is an object or an object description. If it is then the number of the object will be stored in CNUM.

Now that we have added the variable to the command word array, we can assign the object number in the routine that we made earlier. We add this statement:

REM ASSIGN THE OBJECT NUMBER
BWORDS(WT_INDX).CNUM = I


Now, let's look at the routine after we added the above line of code:



Notice that we assigned the CNUM = I. 'I' is the current object count, the object's number. Thus, if the 'sword' is the second object then the CNUM variable will = 2. This is good, because when we process the command we'll already know what object the player is talking about.

Then we can look at the object attributes to see if the player can use the object with the command he entered. For instance, if the player tries to take the mailbox, we just look at the object number and find the attributes for that object number. The attribute for taking the mailbox object is 0, i.e. false.

So, far so good. Now we are going to identify types that are object descriptions.

Right now, we don't have any object descriptions in the game yet. So the first thing we need to do is add a couple of descriptions to the objects. In this game, objects can have one or two descriptions. For instance, the mailbox will have a description named 'small'. We will also add the 'elongated brown sack'. As you can see, the sack has two descriptions.

We add the object description variables to our current object array:

REM CREATE A TYPE DEFINITION FOR OBJECTS
TYPE OBJ
NAME AS STRING
DESC1 AS STRING
DESC2 AS STRING
LOCATION AS INTEGER
INVENTORY AS INTEGER
ENDTYPE


We named the variables, DESC1 and DESC2. If an object has only one description or no description at all then we just make it an empty string.

So, the data statements for these two objects in the array are:

DATA "mailbox", "small", "", 1, 0
DATA "sack", "elongated", "brown", 2, 0


Object Name, Desc1, Desc2, Location, and Inventory

Notice there is only one description for the mailbox. DESC2 is just an empty string ""

We assign the word type for object descriptions by looking at the DESC1 and DESC2 variables of the objects. If the command word matches any of these descriptions then the word type will = 2.

Now, let us go back to the For-Next loop in the type routine. After we check to see if the word is an object, we then check to see if it is an object description:

REM FOUND OBJECT DESCRIPTIONS MATCH
IF BWORDS(WT_INDX).CWORD = OBJ_ARRAY(I).DESC1 OR BWORDS(WT_INDX).CWORD = OBJ_ARRAY(I).DESC2


So, if the command word is Desc1 or Desc2 then we assign the type as a description:

REM ASSIGN TYPE TO ARRAY
BWORDS(WT_INDX).CTYPE = 2


Remember a bit earlier, when we assigned the type for objects, we then added a variable to include the object number too? Well, we are also going to assign the object number to the same variable for object descriptions.

The object number will tell us what object that description goes with. If the player says 'brown mailbox', we need to look at the word 'brown' and if it is a description for the sack and not the mailbox, then we know that 'brown mailbox' isn't correct and we tell the player there is no brown mailbox.

We assign the object number in the same way as before:

REM ASSIGN THE OBJECT
BWORDS(WT_INDX).CNUM = I


Now, here is the routine with the new code for description types:



Everything is the same except we added a second IF statement for description types. In the next section we will add the verb, prep and conj types. After that, we will go on to Step 3 where we will process the commands.

Coding Step 2 - Continued

Identify Word Types - Verbs, Prepositions and Conjunctions

Now that we have identified word types for objects and object descriptions, we will identify them for the verbs, preps and conjs. First, we will make an array to store these words. It will be called the keyword array.

TYPE KW
KWORD AS STRING
KTYPE AS INTEGER
ENDTYPE


There are two variables in the keyword array. One to store the word (verb, prep or conj) and one to store it's type (1, 4 or 5).

MAX_KW = 5 : REM TOTAL KEYWORDS

DIM KW_ARRAY(MAX_KW) AS KW

FOR I = 1 TO MAX_KW
READ KW_ARRAY(I).KWORD
READ KW_ARRAY(I).KTYPE
NEXT I

DATA "take", 1
DATA "drop", 1
DATA "go", 1
DATA "on", 4
DATA "and", 5



For now, we will have just five words. The first three are verbs, the fourth is a prep and the last word is a conjunction. We will place this array definition along with the other ones at the start of the program code.

Now that we have defined the keywords, let's include them in the loop where we identify word types. We already have identified object and object description types in the loop. Right below that code we will include a small routine to see if any of the command words match the keywords from the array. If they match, we will assign either a verb type, conj or prep type.

We'll start it with the usual For-Next loop:

FOR I = 1 TO MAX_KW

Now, compare the command word to the keyword:

IF BWORDS(WT_INDX).CWORD = KW_ARRAY(I).KWORD

If it matches, assign the type:

BWORDS(WT_INDX).CTYPE = KW_ARRAY(I).KTYPE

That's it!

ENDIF
NEXT I


Now we have compared all the command words to every type of word in the game. We are almost ready to start processing the commands, but before we do that we should take care of a few things. What if a command word does not match any of the words in our game? We need to send a message to the player informing him that we don't know what that word is. Let's put some code in the routine that does that:

IF BWORDS(WT_INDX).CTYPE < 1
PRINT "I don't know the word '" + BWORDS(WT_INDX).CWORD + "'."
EXIT
ENDIF


This checks the word type, and if it is less than 1 then no word type has been assigned. That means the command word did not match any of the words in the game, so we return a message informing the player. We can also check something else while we're at it.

Earlier in the tutorial I said that every command starts with a verb, an action. What if the player enters a command that does not start with a verb? What we can do is check the first command word and if it is not a verb we return another message to the player:

IF WT_INDX = 0 AND BWORDS(WT_INDX).CTYPE <> 1
PRINT "That sentence isn't one I recognize."
EXIT
ENDIF


This code looks at the command word index and if it is zero then that means the loop is on the very first word in the command array. Then it looks at the word type, and if it does not equal a 3, which represents a verb type, then this command is not a valid command sentence because it does not begin with a verb. We then return a message informing the player.

*In Zork, there can be a few commands that don't start with a verb, but for this tutorial we will stick with the first word is a verb rule.

Well, we have a couple of command messages here that we may want to use again in some other part of the game. What we can do is make a 'Select-Case' code and just reference each message whenever we need to. Let's make a function that will contain the messages in a Select-Case clause.

First, is the function declaration:

FUNCTION COMMAND_MSG(MSG_NUM AS INTEGER, MWORD AS STRING)

The name of the function is COMMAND_MSG. The function will accept an integer and a string. The integer, MSG_NUM, will identify the message number.

For instance, if MSG_NUM = 1, then we will display the first message. The MWORD string is a command word which is either not recognized by the game or not understood in the way it is used. I will soon show you an example which will make this clear.

After the function declaration we make the rest of the function which consists of the messages in the Select-Case clause:

SELECT MSG_NUM
CASE 1
PRINT "That sentence isn't one I recognize."
ENDCASE
CASE 2
PRINT "I don't know the word '" + MWORD + "'."
ENDCASE
ENDSELECT
ENDFUNCTION


So, let's take an example. The player enters this command: 'Take Playstation'. Since there is no 'Playstation' in this game we need to return a message informing the player just like we did earlier. This time we will use the message function. Right now there are two messages in the message function. The message we want to use for this example is the second message in Case 2. The game does not know the word 'Playstation'. So, in the message function, we assign 2 to the MSG_NUM variable, which represents Case 2, or the second message. Then we assign the word, 'Playstation' to the MWORD variable. So our function call will look like this:

COMMAND_MSG(2, "playstation")

In the actual game, we will not hard-code the word 'playstation' in the function code. We don't know what the actual command word will be but we do know that whatever it is, it is in the command word array. So the actual function call will be:

COMMAND_MSG(2, BWORDS(WT_INDX).CWORD )

In many cases, we do not need to pass a word to the message function. For instance, the first message in case 1 does not use any command word. In those cases, we just pass an empty string:

COMMAND_MSG(1, "")

And that's about it for the message function. Here's the entire function so far:

FUNCTION COMMAND_MSG(MSG_NUM AS INTEGER, MWORD AS STRING)
SELECT MSG_NUM
CASE 1
PRINT "That sentence isn't one I recognize."
ENDCASE
CASE 2
PRINT "I don't know the word '" + MWORD + "'."
ENDCASE
ENDSELECT
ENDFUNCTION


We will add more messages as we need them in the game.

Now that we have a command message function, we will call it for any messages instead of typing in the same message each time. So let's go back to the previous code where we printed the unknown word message:

IF BWORDS(WT_INDX).CTYPE < 1
PRINT "I don't know the word '" + BWORDS(WT_INDX).CWORD + "'."
EXIT
ENDIF


Let's change that code to use the new message function:

IF BWORDS(WT_INDX).CTYPE < 1
COMMAND_MSG(2, BWORDS(WT_INDX).CWORD )
EXIT
ENDIF


We also had a message for any command that did not start with a verb:

IF WT_INDX = 0 AND BWORDS(WT_INDX).CTYPE <> 1
PRINT "That sentence isn't one I recognize."
EXIT
ENDIF


Let's also change that code to use the new message function:

IF WT_INDX = 0 AND BWORDS(WT_INDX).CTYPE <> 1
COMMAND_MSG(1, "")
EXIT
ENDIF


This command function is not really required but it makes it convenient to send messages. It is entirely up to you if you want to use the function or not, but it is there for you. We will add a couple of more messages to it as the tutorial progresses.

Now, here is the complete word type subroutine and below that is the message function at the end of the program:



Well, we have accomplished a lot. We put all of the command words into an array. We identified all of their types and created a message function too. Here is the listing of the complete game code thus far:



Now it is time to process the commands!
Zotoaster
19
Years of Service
User Offline
Joined: 20th Dec 2004
Location: Scotland
Posted: 27th May 2008 01:45
Sorry pluto, I missed this thread. Looks pretty feature rich, so yeah I'll add it to the tutorials thread

Don't you just hate that Zotoaster guy?
pluto
16
Years of Service
User Offline
Joined: 18th May 2008
Location:
Posted: 27th May 2008 02:21
Thanks Zotoaster!
pluto
16
Years of Service
User Offline
Joined: 18th May 2008
Location:
Posted: 29th May 2008 05:02
I will be posting the next section on the parser by this weekend. My time during weekdays is limited due to a busy work schedule, so it takes me a bit longer to complete the tutorials until the weekend rolls around.
Jaeg
18
Years of Service
User Offline
Joined: 16th Mar 2006
Location: Indiana
Posted: 29th May 2008 23:21
Can't wait. I am interested in how you are doing the parser. Something I am having issues with when improving your engine is when I use LOWER$ I have to change the resolution and it seems to be screwing with the blinking "_" of the input. Sometimes it leaves an underscore under the letter.

If you get mad and want to type something nasty about another person do this-Type what you want to say in the box then press ctrl-a and hit delete then type what you should say.
pluto
16
Years of Service
User Offline
Joined: 18th May 2008
Location:
Posted: 30th May 2008 02:12
Not sure why that would happen. You might try changing the font or set text to normal, in the beginning of the program and see if that helps. Something like this maybe.

SET TEXT FONT "TIMES NEW ROMAN"
SET TEXT SIZE 16
SET TEXT TO NORMAL
Jaeg
18
Years of Service
User Offline
Joined: 16th Mar 2006
Location: Indiana
Posted: 30th May 2008 03:08
I tried it and it didn't work.
I attached a picture of what it is doing.

If you get mad and want to type something nasty about another person do this-Type what you want to say in the box then press ctrl-a and hit delete then type what you should say.

Attachments

Login to view attachments
pluto
16
Years of Service
User Offline
Joined: 18th May 2008
Location:
Posted: 31st May 2008 17:53
Well, I tried using LOWER$ and the font size changes and there is no cursor. I notice this happens with some of the other text functions like MID$ and LEFT$ etc. So, something is going on, maybe a bug?
Jaeg
18
Years of Service
User Offline
Joined: 16th Mar 2006
Location: Indiana
Posted: 31st May 2008 18:02
They are all functions designed for the TEXT command. So it seems natural that it messes with print and input. Is there an input command that corresponds with TEXT?

If you get mad and want to type something nasty about another person do this-Type what you want to say in the box then press ctrl-a and hit delete then type what you should say.
pluto
16
Years of Service
User Offline
Joined: 18th May 2008
Location:
Posted: 2nd Jun 2008 01:54
I finally finished the second part of the tutorial for the PARSER! It is posted directly under the first part.

Continuing to work on next section which I hope to have ready this coming week.
pluto
16
Years of Service
User Offline
Joined: 18th May 2008
Location:
Posted: 2nd Jun 2008 01:55
Jaeg,

The only input command I'm aware of is in the core commands.
Jaeg
18
Years of Service
User Offline
Joined: 16th Mar 2006
Location: Indiana
Posted: 3rd Jun 2008 21:13
Couldn't you use a large select case to decide what a word is? It might require multiple select cases. One to identify if it is a verb, noun, ect. And once it gets that information another that based on the verb uses it.

If you get mad and want to type something nasty about another person do this-Type what you want to say in the box then press ctrl-a and hit delete then type what you should say.
pluto
16
Years of Service
User Offline
Joined: 18th May 2008
Location:
Posted: 4th Jun 2008 03:17 Edited at: 4th Jun 2008 04:04
Are you talking about something like this?



I could see how that might be setup. Perhaps a select-case inside of a gosub routine or function that would get executed for every word in the command. Yet, it may get a little awkward as your game gets larger with lots of words. That would be one giant select-case, and for every word you would need a statement to assign it's type. Let's say you have 30 objects in your game and each object has one to two descriptions, that's 60 to 90 case statements with a type statement added to each. Then you need case statements for the verbs, preps, and conjs too!

Instead, all you have to do is compare the command word to the objects that we already have in the object array, and if it matches then you assign it as a noun type. This is done in a simple loop. The idea is that you already have many of your words in the object array. Using a massive select-case, you end up repeating those words when all you have to do is look at the array.

For instance, if you are looking at the object array, you know that the type will be a noun. So, you only need one assignment statement in a loop: type=noun. However, with a select-case you have to make the assignment for every single word.

The adjectives or the object descriptions, we will put them in the object array as object attributes. So, any word related to objects will already be in one place for us to look at. The other words are mostly verbs along with a few prepositions and a couple of conjunctions. We will make a separate array for them.

So we have one set of words stored for objects and one set for 'verbs, preps, and conjs'. So the loop only has to look at two sets of words in a single loop.

I will cover this more in depth in the next part of the tutorial. If you want to use a select case, I think that would work too, so it is really up to you.
pluto
16
Years of Service
User Offline
Joined: 18th May 2008
Location:
Posted: 8th Jun 2008 23:22
I just added a new section for coding step 2, word types for objects and object descriptions.
Jaeg
18
Years of Service
User Offline
Joined: 16th Mar 2006
Location: Indiana
Posted: 16th Jun 2008 17:27
Cool, thanks this is very helpful!

If you get mad and want to type something nasty about another person do this-Type what you want to say in the box then press ctrl-a and hit delete then type what you should say.
pluto
16
Years of Service
User Offline
Joined: 18th May 2008
Location:
Posted: 17th Jun 2008 05:02
Glad you find it helpful Jaeg!

I should have the next section posted within a couple of days.
pluto
16
Years of Service
User Offline
Joined: 18th May 2008
Location:
Posted: 22nd Jun 2008 02:07
I just completed the final section on identifying word types!

The next part of the tutorial is the long awaited section on processing the commands. Coming soon!
pluto
16
Years of Service
User Offline
Joined: 18th May 2008
Location:
Posted: 22nd Jun 2008 20:42 Edited at: 27th Jul 2008 18:03
Create Zork - Tutorial Part III - PARSER (CONTINUED)

Step 3

Process the words of the commands


Now we get down to the core of the parser where we process and execute the command. The first command we will process is the 'take' command.

To process each command we need to setup a process subroutine. What will the process subroutine do? Remember, earlier in the program we made a subroutine that puts all of the command words into an array. The parser routine will call this subroutine. Next, it will call the subroutine that gets the word types. Finally, it will call the subroutine that executes the command.

We will name the parser subroutine 'PARSER':

PARSER:

GOSUB WBATCH


As you can see, the first thing we do in the PARSER routine is call the 'WBATCH' subroutine which puts all of the command words into an array. Next it calls the 'WTYPE' subroutine to get all of those word types:

GOSUB WTYPE

The next step in the 'PARSER' subroutine is to execute the command. Before we do that, we will make a couple of variables to hold the object numbers. This isn't needed but it helps keep the code clearer. Right now the objects are in the command word array, BWORDS(). If a word is an object and we would like to know what that object number is, we have to use 'BWORDS(1).CNUM'. What we will do is assign that array variable to the variable OBJECT1 or OBJECT2. That way instead of having to use 'BWORDS(1).CNUM', we can use 'OBJECT1' which is shorter and a little easier to work with. So, before we call the subroutine that executes the 'take' command, we will set the 'OBJECT' variables to 0:

OBJECT1 = 0
OBJECT2 = 0


The reason we have two object variables is because most commands that the player enters will either consist of one or two objects. For instance, the player could enter the command, 'TAKE SWORD', which contains only one object, or the player could enter the command, 'TAKE SACK FROM TABLE', and this command has two objects, 'SACK', and 'TABLE'. That is why we use two object variables, 'OBJECT1' and 'OBJECT2' that we work with as we process the command. The idea is to get the two objects from the command array and assign them to those two variables. It makes it easier to work with two simple variables instead of the actual array variables.

After that, we now execute the command. We will use a Select-Case to process each command:

SELECT BWORDS(0).CWORD
CASE "take"
GOSUB CMD_TAKE
ENDCASE
ENDSELECT


We start with the 'take' command. If the first word of the command sentence is 'take' the 'CMD_TAKE' subroutine is executed. Every time we add a new command it will go in this Select_Case clause. Now we need to make the 'CMD_TAKE' subroutine.

CMD_TAKE:

The first thing we do is assign the first object to the 'OBJECT1' variable.

OBJECT1 = BWORDS(1).CNUM

Next, assign a '1' to the object's inventory variable which signals that this object is now in the player's inventory.

OBJ_ARRAY(OBJECT1).INVENTORY = 1

After that, assign a '0' to the object's location variable which signifies that the object is no longer in the area of the player's location.

OBJ_ARRAY(OBJECT1).LOCATION = 0

Finally, we inform the player that the object has been taken:

PRINT "Taken."

And here is the code for the 'CMD_TAKE':



That is the very core of the take command. Now, let's add the 'drop' command. It works almost exactly the same, we just reverse the inventory and location values. The first thing we do is add the 'drop' command to the Slect-Case clause, right below the 'take' command:

SELECT BWORDS(0).CWORD
CASE "take"
GOSUB CMD_TAKE
ENDCASE
CASE "drop"
GOSUB CMD_DROP
ENDCASE
ENDSELECT


This will call the 'CMD_DROP' subroutine. Let's make that subroutine:



Notice, it is almost the same as the take routine except the values have been switched. The object is no longer in the player's inventory, so the inventory variable is '0' and the object is put back in the area of the player's current location, so the location variable is = PLR_LOC.

Well, we have made the take and drop routines but we are not done with them yet. There are still some things that need to be dealt with.

Before we let the player take any object, we need to make sure that the object is at the player's location. We need to make sure that the player doesn't already have the object! And we need to make sure that the object can be taken. If the object is something like a house, then the player should not be allowed to take it.

First, let's check to see if the player has the object already. If he does have it then we need to display a message informing the player that he already has it. We will add this new message to the message function which we made earlier in the tutorial. The message will be, 'You already have that!'. Let's add that message to the message function:



As you can see it is the third message, so when we call the function, we will pass the number '3' to it. So, let's go back to our 'CMD_TAKE' subroutine and check to see if the player already has the object:

IF OBJ_ARRAY(OBJECT1).INVENTORY = 1

We look at the object array, and if the inventory variable is = 1 then the player does have the object, so we call the command message function which informs the player of that:

COMMAND_MSG(3, "")
RETURN
ENDIF


Notice, we passed the number '3' to the function which calls the third message that we just added.

Now, if the player does not have the object, we will check to see if the object is at the player's location. For instance, if the player tries to take the sword, but the sword is not at his location then we send the message, 'You can't see any sword here!". Let's add that message to the command message function, which will be the fourth message:



Going back to the 'CMD_TAKE' routine, we check to see if the object is at the player's location:

IF OBJ_ARRAY(OBJECT1).LOCATION <> PLR_LOC

If it is not, then we call the command message function:

COMMAND_MSG(4, OBJ_ARRAY(OBJECT1).NAME)

This will return message number '4' which we just added. Notice we passed the number '4' and also the name of the object to the function.

Now, let's take a look at the 'CMD_TAKE' subroutine:



The code is relatively simple. It first checks to see if the player has the object already, then it checks to see if the object is at the player's location and finally it gives the object to the player and removes it from the current location.

What about the DROP command? Really, the only thing we need to check there is if the player has the object, because if he doesn't then he cannot drop something he doesn't have. So, as before, we will add a new message to the command message function that says, 'You don't have that!'



This is message number '5', and we will send this message whenever the player tries to drop something he doesn't have.

REM IF PLAYER DOES NOT HAVE THAT OBJECT
IF OBJ_ARRAY(OBJECT1).INVENTORY = 0
COMMAND_MSG(5, "")
RETURN
ENDIF


Here is the DROP command routine:



And here is the complete game code so far:



Go ahead and run it. Try taking and dropping the objects. Try dropping an object you don't have or taking an object that you already have.

You may have noticed that you are able to take the mailbox. There are some objects in the game which the player should not be able to take such as the mailbox. In the next section we will make a 'TAKE' attribute for objects and if that attribute is a negative number then the player will not be able to take it. After that we will handle object descriptions. Also, in the next section we will process two objects in the take command, such as 'Take sack from table'. We will make a table and a sack in the kitchen. You will be able to put the sack on the table and take it from the table. Then we will put stuff in the sack in the same way Zork works. See you then!
Jaeg
18
Years of Service
User Offline
Joined: 16th Mar 2006
Location: Indiana
Posted: 15th Jul 2008 23:26
Cool. Excellent addition to the series!

If you get mad and want to type something nasty about another person do this-Type what you want to say in the box then press ctrl-a and hit delete then type what you should say.
pluto
16
Years of Service
User Offline
Joined: 18th May 2008
Location:
Posted: 16th Jul 2008 05:18
Thanks!

I will post the next section this weekend. It's been awhile since my last update as I was on vacation this summer. Next section we will make more attributes for the objects and work with two noun commands. Once you see how the two-noun command works you will be able to make your own custom commands because they are all patterned the same way.
pluto
16
Years of Service
User Offline
Joined: 18th May 2008
Location:
Posted: 30th Jul 2008 02:17
Ok, I've finished coding the next section and I'm almost ready to post it after a few more touch ups.
Jaeg
18
Years of Service
User Offline
Joined: 16th Mar 2006
Location: Indiana
Posted: 1st Aug 2008 03:56
Can't wait. I've been using a modified version of your engine at the early stages to make a text adventure for the competition.

If you get mad and want to type something nasty about another person do this-Type what you want to say in the box then press ctrl-a and hit delete then type what you should say.
pluto
16
Years of Service
User Offline
Joined: 18th May 2008
Location:
Posted: 4th Aug 2008 04:37 Edited at: 4th Aug 2008 04:39
Ok, I have made the container objects for the game. What exactly are containers? A container is any object that can contain another object. The best example of this is the brown sack on the table inside of the house. In Zork I, the player can take stuff out of the sack and put stuff into the sack. The sack is a container object.

Also, the table is a container object. The player can put stuff on the table, such as the sack or the bottle of water. Any object that can hold another object is a container. If the player can put something inside of it or on top of it, then it is a container.

Container objects are tricky and can be the most difficult part of the game to program. What can make them diifficult is that you can have containers inside of other containers. Sophisticated text adventures such as Zork allow the player to put one container inside of another. There may even be many containers, one inside of another. For instance, you may have milk in a carton, and the carton is in a lunch sack and the sack is inside of a cooler and the cooler is on top of the table!

The best games have almost a limitless container ability. That is, you can have as many containers inside of other containers as you wish. It's up to you. This is the approach I took, and in order to program limitless containers I used a 'recursive function'. That is a function that calls itself. This function displays all the items at the player's location, including every container. It lists items in a container object and it has to call itself whenever one of the items in the container object is also a container!

You may remember the kitchen in Zork. There was a table and a sack with garlic and lunch. I have added those objects to the game. Also, the leaflet is now inside of the mailbox. You may take it out and you can even put the leaflet back into the mailbox. Same with the sack. To put the sandwich in the sack you would enter the command, 'PUT SANDWICH IN SACK', or 'PUT THE PEPPER SANDWICH INTO THE BROWN SACK', or 'PUT THE HOT PEPPER SANDWICH INSIDE OF THE ELONGATED BROWN SACK'. Any variations of those commands will work!

I am going to post the game code now. Instead of explaining every line of code for this, I will go over the basic logic. Also I will show you what you need to do to put your own custom containers in the game. Don't worry, the code is already there, now it will be as simple as adding your own locations. For instance, this is the line of code I used to make the brown sack on the kitchen table:

DATA "sack","brown","elongated",0,0,1,1,4,0,1,5,0

That's about all there is to it!

Anyway, I will walk you through making your own containers step by step in the next session. Also, we will make the 'open' and 'close' commands. Once we have containers out of the way, the rest of it is pretty simple stuff. Following that part of the tutorial, we will make the troll and have the player fight him!

Here's the game code:

pluto
16
Years of Service
User Offline
Joined: 18th May 2008
Location:
Posted: 6th Aug 2008 06:01
Hey, Jaeg, good luck on the competition! I'd like to play it whenever you get done.

I am almost finished with the containers for the game engine. The code I posted earlier is workable but is not completed just yet. I will have it soon. Also, I'm adding the 'open' and 'close' commands for container objects. The container objects will have limited capacity. You will be able to make the container whatever size you like and it's holding capacity. For instance, a large wooden crate may hold 1,000 'units' but a small jewelry box will only fit 1 unit. You will be able to make as many container objects as you like. For instance, you could make a chest and inside the chest could be a small flask filled with a strange liquid. Whatever your imagination dreams up and the game will come alive.

If there is a special object that you need for your game let me know and I will make that object type. I'm always ready to help, if you have any questions or need assistance.
Fangs
16
Years of Service
User Offline
Joined: 24th Oct 2008
Location: 40 some miles North of the Emerald Coast
Posted: 24th Oct 2008 23:09
Hey Pluto

I just got my DBPro D/Led and working today. This also my first post to the forum. I have been using the trial copy of DBPro to do some of the tutorials.

Your tutorial (Zork, part1) is my first shot using the full DBPro. I did have a little problem with the final update code. When I run the code I came up with a Run Time Error - "Missing data for MAX_LOC or data out of range". Appernetly the program didn't know the size of MAX_LOC, so I declared MAX_LOC = 7 and everything worked.

The Fangman
pluto
16
Years of Service
User Offline
Joined: 18th May 2008
Location:
Posted: 25th Oct 2008 04:56
Hi Fangs!

Yes, MAX_LOC = 7, is correct.

Good luck on the tutorial. If you have any more questions feel free to ask.

-Pluto
pluto
16
Years of Service
User Offline
Joined: 18th May 2008
Location:
Posted: 25th Oct 2008 05:13 Edited at: 25th Oct 2008 05:22
Here is an updated version of my 'zork engine'.

Feel free to use it as you wish.

It has one of the best 'container' handling capabilities out there. Containers such as chests, trunks, sacks and desks are the most difficult part of making a text adventure. My game engine can handle all containers and even containers within other containers.

If you want to make your own game with this code but need help, just drop me a message here. Let me know what you want to do and I will help.

Most of all, have fun!

Fangs
16
Years of Service
User Offline
Joined: 24th Oct 2008
Location: 40 some miles North of the Emerald Coast
Posted: 25th Oct 2008 18:42 Edited at: 26th Oct 2008 01:43
Hi Pluto

Thanks for the updated code. I run it seperately from the tutorial source I've been working on.
Your updated code will not compile. Every instance of "<"/">" halts compilation dead in it's tracks. Call me dense but I couldn't figure it out.

As for the tutorial code... I've had a few problems, but nothing I couldn't solve. I've added here and there to the code just to see what will work and what won't. All in all, very instructional.

Oh, by the way... I was around when "Hunt the Wumpus" and "Collosal Cave" first turned up.

My Mad Cow infection must be rearing it's head big time!!

The Fangman
pluto
16
Years of Service
User Offline
Joined: 18th May 2008
Location:
Posted: 26th Oct 2008 16:56
I'm not sure why it won't compile for you. I copied the code as it is from my editor and it compiles and runs without any problems. I am using the most recent DBPro and editor versions.

Those symbols, "<" and ">" are 'Less Than', 'Greater Than' operators and should compile without any problems.

If you can, let me know the first line in the code that won't compile and I'll take a look at it. Once I have a specific line of code than I'll have a better idea of what might be the problem. With any luck we'll be able to figure it out.

Yes, I would encourage you to make your own changes to the code and experiment with it. That's a good way to learn how the code works.

Hunt the Wumpus, and Collosal Cave were the forebearers of modern Interactive Fiction. I was about three years old at the time they came out. I actually wrote a smaller version of Hunt the Wumpus on my Commodore 64. My favorite game though was Zork and I was totally blown away by it. I remember my first attempt at making a game like zork and how hard it was for me. I wrote several games but they weren't as sophisticated. It took me some time before I figured out how to do it. Now I wanted to share that knowledge with anyone who is interested and maybe save them some of the headaches I went through.

The nice thing about Zork and all interactive fiction is that you can make a very compelling game that doesn't require an army of programmers and graphic artists or 3d modellers. It's just you and whatever story you can imagine.

There is also a good market for IF. One of the most prolific IF writers has his own shop:

http://www.malinche.net/
Fangs
16
Years of Service
User Offline
Joined: 24th Oct 2008
Location: 40 some miles North of the Emerald Coast
Posted: 27th Oct 2008 01:58
Hey Pluto

I didn't have a problem with the <> (less than, not equel, greater than) symbols. The problem was how they were written in your code.
An ampersand(x) in front: (x)lt and (x)gt. I put ampersand+lt and ampersand+gt in my message and it came out <> when it was posted here.

Anyway, after I figured out (dumb!!) what the lt and gt stood for I did a find and replace and the code run great.

This morning (6am) I started filling out the code from the Zork map. So far I have 21 rooms that work and all the items from the map to be found in the rooms. I also added the 4 other cardinal directions. I don't remember all that much of the game like hidden rooms, hidden items, and the goal of the player.

Right now I am having a problem with the bird's nest and the jewel encrusted egg. The nest shows up in the tree, and the egg shows up in the nest. I can take the nest with the egg in it, but I can't take the egg from the nest. A hint to the problem is that the program prints out the name (egg), and the first description, but not the second.

data "nest","bird's","",11,0,1,1,0,0,1,0,0,0,0,0,0,0,0 Item #15
data "egg","jewel","encrusted",0,0,1,1,15,0,1,0,0,0,10,3,0,0,0

One thing for sure, this past few days has been a learning experiance, especialy today. Things to do: implement read and use comands.

One last thing! I seem to remember a text game about a guy trying to survive on a runaway space ship with all sorts of baddies trying to do him in. I've been toying with idea of making a run at it now that I have your game engine.

The Fangman
Fangs
16
Years of Service
User Offline
Joined: 24th Oct 2008
Location: 40 some miles North of the Emerald Coast
Posted: 27th Oct 2008 14:31
Pluto

I found the culprit, I think. I have the mailback feature enabled so I receive your posts to the forum as an Email. Both messages, the updated code and your reply to my post, had the < and > shown as (x)lt and (x)gt (x=ampersand). Wierd!

The Fangman
pluto
16
Years of Service
User Offline
Joined: 18th May 2008
Location:
Posted: 30th Oct 2008 04:28 Edited at: 30th Oct 2008 04:59
Fangs

Glad to hear you figured out the problem, good deal!

I'm amazed at how well you've picked up on the code here. There are some things which I haven't covered yet so if you come across anything that's not in the tutorial just let me know.

In order to take the egg, you will have to assign it's 'C_LOCATION' the same location number of the nest.

For instance, the nest is located at location '11'. So the egg's 'C_LOCATION' should equal '11' - It is at the same container location as the container.

Here is the data statement for the egg, with the number '11' added to it's C_LOCATION.

data "egg","jewel","encrusted",0,0,1,1,15,0,1,11,0,0,10,3,0,0,0

Now about the descriptions of the egg, being the 'Jewel Encrusted Egg' I did not display both descriptions but you can change the code to do just that. Find the 'DISPLAY_OBJECTS' subroutine and add the second description:

IF LEN(OBJ_ARRAY(I).DESC1) > 0
PRINT "There is a " + OBJ_ARRAY(I).DESC1 + OBJ_ARRAY(I).NAME + " here."


There are also some other parts of the code that can be changed as well.

There was a game called 'Starcrossed' which I think may be the runaway ship but I'm not certain. That would definitely make a great setting for such a game. I'll be here to help if you run into problems.
Fangs
16
Years of Service
User Offline
Joined: 24th Oct 2008
Location: 40 some miles North of the Emerald Coast
Posted: 30th Oct 2008 13:16
Hi Pluto

Glad to here from you. I figured out the 'egg' and moved on. I have added 4 new rooms;
Trapdoor under the 'oriental rug' going to the celler.
A loose panel in the 'trophy case' going to a strange passage.
The 'grate' room in the forest clearing.
a 'subterranium' room connecting the 'grate' room with the 'strange passage'.

All locations work. The problem I'm having now is that if you are in the living room and 'go down' you are in the celler without having to move the rug or open the trapdoor. The same is true with the loose panel in the 'trophy case'. You can 'go north' with out opening the case and/or the loose panel.

To add to my frustrations, I am having issues with DBPro. The error reporting bug (reporting an error as some completely irrelevent line #) is driving me nuts. I understand that this problem is addressed in the latest update(7.0) which I have been unable to install.

The Fangman
Fangs
16
Years of Service
User Offline
Joined: 24th Oct 2008
Location: 40 some miles North of the Emerald Coast
Posted: 30th Oct 2008 14:47
Hey Pluto;

Acouple of things I forgot to mention in my last post.

I've lost my cursor! I haven't tried to track that down as yet.

I have declared four new objects; iron grate, trapdoor, wood panel, and oriental rug.

So far they all work (you can open and close, or take) to a degree.

Right now I have some error that is stoping me in my tracks. I haven't been able to find it yet...but.

I have good days and I have my bad ones. On the good ones I can look a the code and there is the problem staring at me.

On my bad, I can't find my rear end unless I use both hands and sometimes even that isn't enough!

The Fangman

Login to post a reply

Server time is: 2024-11-24 19:24:11
Your offset time is: 2024-11-24 19:24:11