Programming C, C++, Java, PHP, Ruby, Turing, VB
Computer Science Canada 
Programming C, C++, Java, PHP, Ruby, Turing, VB  

Username:   Password: 
 [Tutorial] Enum - Expansion and Applications of Enum
Index -> Programming, Turing -> Turing Tutorials
View previous topic Printable versionDownload TopicRate TopicSubscribe to this topicPrivate MessagesRefresh page View next topic
Author Message

PostPosted: Sun Aug 26, 2007 4:59 am   Post subject: [Tutorial] Enum - Expansion and Applications of Enum

preInitNote: [username:6a8acbb851] please be warned! This has yet to be edited, and was written at 5:30 in the morning. It will be edited, and this message removed. So if you are reading this [username:6a8acbb851], then it has yet to be edited.
initNote: This really does go somewhere eventually, I swear. It seems random, but it makes sense by the end, I promise. So keep on reading [username:6a8acbb851].

I recently came across enum and found it extremely useful (You know how it is. Bored, so you decide, "Hey, why not look up stuff in Turing help files?"). While there is already a tutorial on enum on compsci, I feel it doesn't give most readers it's full scope. So, in that regard, this tutorial will attempt to show you some interesting things that can be done with enum. Of course you can always expand whats here, and I encourage everyone to come up with new ways of using enum (or other tools of any programming language).

Now a brief run through of enum:

enum cannot be used like most types (int, string, char, boolean, etc). You cannot have "var pickles : enum (Pizza, Toast, SuperMan)". You need to first make your own type of enum, which is simple;

type enumTypeName : enum (id1, id2, id3, id4)

With that, we can declare variables with our enum type. I first came across enum when I was looking for a way around using tons of if statements. While if statements aren't bad, I try to avoid if'ing as much as I can. While you can't avoid them (arguable, not important), writing code without them usually makes code have a lot less clutter, and sometimes more efficient, depending on the situation. For instance, I changed a 15 line procedure using if's, into a 3 line procedure using enum and strings, which is what started me to find ways to use enum to my advantage.

Here is the cutdown code:
procedure ChangeDir (dir : string)
    curDir := storedDirs (index (stringDirs, dir))
end ChangeDir

Here are the variables used above:

type dirENUM : enum (u, d, r, l)
var curDir : dirENUM
const storedDirs : array 1 .. 4 of dirENUM := init (dirENUM.u, dirENUM.d, dirENUM.r, dirENUM.l)
const stringDirs : string := "UDRL"

You can see that our enum type has 4 id's: u, d, r, l. Which correspond to Up, Down, Left, and Right (respectively). I will note here, that I in no way expect or want people to follow my naming conventions; its just bad style. Our magical enum is dirENUM, which is 'direction enum'. curDir is a variable that stores which direction the player is currently facing. So obviously, the ChangeDir procedure changes the direction the player is currently facing.

The old code used to be [if dir = "u" then; curDir := dirENUM.u; end if], and so on. Copy and paste, change the values, doesn't work for me. It takes up too much space, and when you get large programs you have to either subprogram them (which would be fine with a turing IDE with tabbed browsing or archiving, but we don't have one).

How the code works:
storedDirs is an array from 1 to 4. Each of them corresponds to one of the values of the enum type, dirENUM. So when I say put storedDirs(1) it would printout 'u', because thats the first enum type. Now, with that in mind we can declare other enum's of dirENUM by using storedDirs. You may want to reread that a few times. In other words, if I have:

curDir := storedDirs (1)

Then curDir becomes the enum 'u', because thats what we declared as its first element. So with that, we can using indexing! Which means, searching for a string in another string. stringDirs is a string of "UDRL". So if someone wants to change the direction to up, they call ChangeDir ("U"). We index for U, and find that it's in the first location of stringDirs, and what else has u as the first location? storedDirs! So when we have a string in the same order as our stored enum's, then we can just use indexing to change the values around.

Now you could just change "ChangeDir" to accept enum u instead, but what do we do in the case of modules? When we handle everything behind the scenes, and the original program wants to change the direction? Well we could export the enum type, or we could use this method.

Now, using enum so far is useless. It doesn't do anything. The real beauty, and the original idea for this, was for sprites. You have 8 sprites, 2 for each direction. Here is the code for drawing sprites:

procedure pDraw
    Pic.Draw (picDirs (curDir) (curSprite), round (x), round (y), picMerge)
end pDraw

First, the Cycle procedure is of little importance. It simply cycles through the current sprites (which are determined in an assorment of ways which isn't important right now, but it does use enum).

Secondly, notice that we gain have curDir, which is our enum type [dirENUM]. The actual variable of picDirs is;

var picDirs : array dirENUM of array 1 .. 15 of int

This is an array of an array. It could also be made into a 2D array like this;

var picDirs : array dirENUM, 1 .. 15 of int

I prefer the first method, for unimportant design features.

Now, back on topic. picDirs is an enum array of an array of integers. Which means it can store picture reference's (15 of them in this case) for each direction. If I wanted the first picture of the sprite for up;

picDirs (dirENUM.u) (1)

I'm kind of disappointed, I thought there would be more to explain. It's pretty much self explanatory. For cycling purposes (through the sprites) I have this variable, again using enum (easy acces with curDir);

var nSprites : array dirENUM of int := init (0, 0, 0, 0)

So when I employ initPics, I store how many sprites going up there are (incase there are 5 going up, and 3 going down). This allows the program a little more room to breath, and a lot less editing if say you wanted to add more up sprites without changing the code at all. Of course you can only have 15 sprites, but that can easily be changed. You could also make it flexible, but I avoided it for a number or reasons. They all added up to "Don't really need it" and "Its too complicated". If you wanted to make them flexible, you wouldn't need nSprites, but you would need to rework the code:

var picDirs : array dirENUM of array 1 .. 15 of int

would become;

var picDirs : flexible array 1..0 of array dirENUM of int

But that doesn't work. It compiles sure, but you can't use it effectively. When you create make the array bigger (lets say you have 5 right sprites);

new picDirs, 5

You don't just make 5 right spots, but 5 up sprites and 5 down and 5 left. So you have unused integers. You end up having to figure out the most amount of sprites and then expanding to fit that need. Of course it can be argued that if I have 15 as I did originally I have will pretty much always have either too many pics, or unused ints.

This could be remedied if we could make the second of array flexible; but we can't. Turing doesn't allow it, and I doubt it ever will. It gets into a bunch of problems, mainly an array that could look like this;



Which may not seem like that much of a problem, but it requires stricter design. It also gets complicated with N-Dimensional arrays, some being flexible, some being regular.

However, this is here nor there.

When I said I disliked if's, or implied it, I was really refering to anything I need to use copy and paste tactics. Lets explore how I transformed this:

for i : 1 .. nSprites (dirENUM.d)
    picDirs (dirENUM.d) (i) := Pic.FileNew (folder + "/D" + intstr (i) + "." + ext)
end for
for i : 1 .. nSprites (dirENUM.u)
    picDirs (dirENUM.u) (i) := Pic.FileNew (folder + "/U" + intstr (i) + "." + ext)
end for
for i : 1 .. nSprites (dirENUM.r)
     picDirs (dirENUM.r) (i) := Pic.FileNew (folder + "/R" + intstr (i) + "." + ext)
end for
for i : 1 .. nSprites (dirENUM.l)
     picDirs (dirENUM.l) (i) := Pic.FileNew (folder + "/L" + intstr (i) + "." + ext)
end for

into this:

for k : dirENUM
    for i : 1 .. nSprites (k)
        picDirs (k) (i) := Pic.FileNew (folder + "/" + stringDirs (locDirs (k)) + intstr (i) + "." + ext)
    end for
end for

For loops need to cycle through values. You can do things like 1..2 or -2312...0+round(sqrt(pi)) or even 'a'..'z'. For loops are even so versatile, they can cycle through enum types! With that, any cycling of an enum type can be done with a for loop; how nifty. Also, you don't have to enum through the whole thing!

I could simply go through the first three elements if I wanted by typing;

for k : dirENUM.u..dirENUM.r

Which would report u, d, and then r before exiting. Just remember that, its a function of enum which could be applied somewhere. Its a tool, use it at your discretion.

So lets look at the code. The first, longer version goes through each direction; Up, Down, Right, then Left (u, d, r, l). Each direction is its own for loop. In each of those, it goes through how many pictures there are (determined in the procedure right before that, but its unimportant code so I didn't included it. I get sidetracked enough. Just know that it calculates the number of sprites for each direction in any given folder). The rest of the code is pretty easy to follow, it simply stores the index of a picture in the picsDir array.

The second code goes through all of dirENUM, which as you recall is four id's (u, d, r, l). So the first one would be enum type u. So for i : 1..nSprites (k) is the same as for i : 1..nSprites(dirENUM.u). That way it works for all of the original four loops. One problem solved.

The second is in finding the sprites, which are labeled with a direction letter, a number, and then the file extension (in this case bmp). To achieve that we need two variables, one which we used before (stringDirs) and a new one (locDirs).

const locDirs : array dirENUM of int := init (1, 2, 3, 4)
const storedDirs : array 1 .. 4 of dirENUM := init (dirENUM.u, dirENUM.d, dirENUM.r, dirENUM.l)

locDirs is an array of dirENUM of int, which means for each id of dirENUM (u, d, r, l) there is a corresponding integer value, which we store from 1..4. When we say put locDirs (dirENUM.d) we get the result 2, because d is the second id. Therefor if we have, put storedDirs (locDirs(dirENUM.d)) we get a string version of d ("D").

An important note. When you put dirENUM.u the result will be u on the screen. This is not a string. If you have;

put dirENUM.u = "u"

It will give an error, saying they are different types. It works for chars as well. I found that out trying to come up with a keyboard system using enum. That is one reason why we have indexing methods.

Back on track;

So in the for loop we have stringDirs (locDirs (k)) which gives us what we had before, except in a few less lines.

Do we NEED enum? No, of course not. Do we like enum? I do. It's useful. It can make code easier to read. Its just a tool. We could program without strings, but they are so convenient. enum can be convenient too.

It has a ton of uses, not just what I stated here. For instance, I did come up with a simple keyboard system;

type key_roots : enum (u, d, r, l, strafe, c1, c2, c3)
var KEYBOARD_CHARS : array key_roots of char
KEYBOARD_CHARS (key_roots.u) := 'w'
KEYBOARD_CHARS (key_roots.d) := 'd'
KEYBOARD_CHARS (key_roots.r) := 'd'
KEYBOARD_CHARS (key_roots.l) := 'a'
KEYBOARD_CHARS (key_roots.strafe) := KEY_SHIFT
KEYBOARD_CHARS (key_roots.c1) := 'z'
KEYBOARD_CHARS (key_roots.c2) := 'x'
KEYBOARD_CHARS (key_roots.c3) := 'c'

You can simply config it any way you want. You don't need to do it this way. You could make a record of keys for instance. Its just simply a way to do things. I wonder if you could use enum to call procedures (without if statements!), and then call certain procedures when you press a key which has been linked to a num. Say you have;

for i : key_roots
put ch (KEYBOARD_CHARS(i))
end for

But instead of a put, you execute procedures? You could change what you press to go up, without changing anything else; the procedure for up would still be the same.

Maybe that's useless, maybe you have a better method, maybe it just gets you thinking about ways to avoid ever having to use enum's ever again. Whatever it is, I hope I've motivated someone in some way, or parted some knowledge (even if it is to avoid my threats Wink)
Display posts from previous:   
   Index -> Programming, Turing -> Turing Tutorials
View previous topic Tell A FriendPrintable versionDownload TopicRate TopicSubscribe to this topicPrivate MessagesRefresh page View next topic

Page 1 of 1  [ 1 Posts ]
Jump to: