Some students are interested in role-playing games. I actually didn't know very much about RPGs until I started teaching programming and my students introduced them to me. I'm not an expert, but here is some of what I understand. In an RPG, the player is a character with certain attributes, such as health and an ability to attack enemies. The more health you have, the better you are doing in the game.
Now suppose I wanted to write a subroutine where two characters have a fight with each other (you versus the enemy). If I am storing multiple pieces of information about each character, there could be a lot of arguments to send into the subroutine. What if each character has 8 attributes? Do I really want to send in 16 arguments?
We know that we can store all of a player's attributes as numbers or strings. Either the information is text or it's numerical and those are the only variable types we have. But wouldn't it be nice if there were other types of variables— if a player could actually be a variable? With the TYPE keyword, you can in fact invent your own type of variable. Here's how it works:
You are essentially making an entry in the dictionary; you are defining what it means to be a player. Once you teach QuickBASIC what to expect for a player, you can actually DIM your variables with that type:
TYPE Player
health AS INTEGER
spell AS INTEGER
characterName AS STRING * 15
...
END TYPE
DIM playerOne AS Player
Each one of the player's characteristics is known as a "field" of the type. You can give the type as many fields as you want. The fields can be numbers or strings. If you decide to use a string for one of the fields, you will need to set a maximum length on that string. That's why you see STRING * 15 in the TYPE statement above. This is because the computer needs to know how much memory to set aside for a variable of type player. Remember that each time you DIM a variable, you are actually allocating a section of memory to store the information.
Once you have created a variable of a certain type, you need to assign values to each of its fields. If you fail to do that, then each of the fields will have only a default value. Numbers default to 0, while strings default to an empty string. To initialize the fields, use this syntax:
Each of the fields can be treated as a variable. You can use it in any code statement where you would ordinarily have the name of a variable. For example, you might have an increment statement:
playerOne.health=800
playerOne.spell= 3
playerOne.characterName = "Lilith"
Or you might have a decision structure like this:
playerOne.health = playerOne.health + 50
or use the field as an argument to a command:
SELECT CASE playerOne.spell
PRINT "Beware, "; playerOne.characterName; "doom awaits you!"
When you create your own type of variable, such as "Player" you can use it just about every place you would use the regular variable types like STRING and INTEGER. Just as you can have an array of strings, you can also have an array of Players. Just as you can send an integer into a subroutine as an argument, you can send a Player into a subroutine as an argument:
After I write that subroutine, I would call it with my player variables as arguments:
SUB letPlayersFight(playerOne AS Player, playerTwo AS Player)
letPlayersFight playerOne, playerTwo
You can even use a Player as part of another TYPE statement:
The only thing you can't do with a Player is use it as a value to return from a function. Functions can only return numbers and strings.
TYPE Game
hero AS Player
numEnemies AS INTEGER
difficulty AS INTEGER
maximumDamage AS INTEGER
...
END TYPE
By the way, the naming convention for types is a little different than the convention for variable names or subroutine names. With type, you should capitalize the first letter of the type name.
I know that was a lot of talking, but I needed to give you the information. You don't need to do anything for #1--just take in the information and see if it makes any sense. If you feel a little shaky on it, tell me now.
Okay now you will do something. Create a type named "Location" and use it to store the x and y values of a point. (You can't call it "Point" because that is already a QuickBASIC keyword.) Then write a subroutine named drawTriangle that takes three locations as arguments and connects them to make a triangle on the screen.
In your subroutine, did you list the three locations separately in the argument list or did you send them in as an array? Which do you think is a better choice? Say why when you send me your code for drawTriangle.
The purpose of the TYPE keyword is to allow you to cluster information together. In an RPG, you want to think of a character as a single object that happens to have many attributes. Similarly, in a geometry program, you might want to talk about a location on the coordinate plane as one thing. It's a location. We happen to describe it with two attributes (x and y) but it is still one location. It's much safer to work with an array of locations because each location will know its own x and y value. If instead you were to have two separate arrays— one for x values and one for y values— you would have to be very careful about your array index values, to be sure that the correct x and y were always paired together. Using the TYPE keyword ensures that the information for an object will always be kept together, because the object is in charge of knowing its own information. Let me know if I am convincing you or not about the value of TYPE. Sometimes it takes a while to see how it helps you.
This problem is also just a reading exercise. Do tell me if it is making sense.
A card game is a good place to apply the concept of TYPE. In my program, I want to talk about a card. That card happens to have a suit and a value, but it is still a card. I want to be able to write subroutines that shuffle cards, deal out the cards, compare cards and so forth. The arguments to those subroutines should be cards, not integers or strings.
Okay, now we need to think about how to set up the fields for type Card. We start by asking ourselves: what does a card have? A card has a suit and a value. So the TYPE declaration will have two fields. But how should we store the attributes? Should they be strings or integers? You can argue it either way. The TYPE declaration could look like this:
TYPE Card
suit AS STRING * 1
value AS STRING * 2
END TYPE
Notice that we can't just say "AS STRING." We have to make what is called a fixed- length string. We have to decide in advance how long the string will be. I chose to make the suit just 1 character ("C" for clubs, "D" for diamonds, etc.) and the value 2 characters, because we need to be able to have "10". Then to create the jack of spades, for example, we would have:
Another way to do it is to use integers in the TYPE declaration:
DIM myCard AS Card
myCard.suit = "s"
myCard.value = "j"
Which do you prefer? Why? Discuss it with me.
TYPE Card
suit AS INTEGER
value AS INTEGER
END TYPE
DIM myCard AS Card
myCard.suit = 4
myCard.value = 11
I see some advantages to using integers because we would be able to do certain tasks, like filling the deck, in a loop. On the other hand, the strings seem clearer, because when you see "j" you know it means "jack." With the integers, it's not at all obvious what numbering system you should use. If you want the two of spades to have a value of 2 and for the aces to have the highest value, then your numbering system would start at 2. You might forget that there is no "1" and make a mistake when you write your loop. And with the suits, are you going to remember that spades are 4? Or will you have to keep looking it up?
There is a way to get the best of both worlds, using a new keyword: CONST. CONST creates variables that are called "named constants." It's a way to give an integer a name, so 4 can be named "spades" and yet still equal the number 4. Named constants are always declared in the main module. They are automatically shared among all subroutines. It looks like this:
Suppose I set up a bunch of named constants for all of the suits and values. Then if I need to check for the queen of spades, I can say this:
CONST clubs = 1
CONST diamonds = 2
It makes your code read very clearly. It also allows you to use the constants in loops:
IF myCard.suit=spades AND myCard.value=queen THEN
From now on, you should use named constants often in your programs.
FOR suit = clubs TO spades
NEXT suit
This problem is yet another reading exercise. You do need to read it all through, and it might take more than one reading to get the idea. Call me over to discuss the idea of CONST.
In the next lab, you will be writing a program that involves the game poker. Think about playing a card game in real life— you need a deck of cards, right? Same with your program for a card game. You need a deck of cards. And what is a deck of cards, really? It's a collection of objects called cards. The cards are not identical because they have different suits and different values, but they are all still cards. An array is a container suitable for holding a collection of similar objects. Use TYPE and CONST to set up an array of 52 cards. Use nested loops to initialize the cards' suit and value. Send me your code.
Your next task is to demonstrate that you are comfortable using TYPE in a large program. You may have already written a program that does that. If so, you can submit it as your evidence of comprehension. If you don't have such a program, you might want to take one of your old programs and think about how TYPE could make the program better. If that doesn't appeal to you, I can give you somebody else's old program and you can convert that one.
When you look through the old program that you are converting to TYPE, you should also look for places where CONST would make sense.
This problem is the biggest problem in the lab. You are working on a large program and rewriting it to incorporate the concepts of TYPE and CONST. You will definitely want to discuss it with me as you go. When you are done, send me your code.
To summarize, in this lab you should have sent me the following things: