|
A set of tutorials for the beginning Be programmer by Brent P. Newhall / 2000 System: BeOS
Hello World, Text Version This tutorial should introduce neophyte programmers to the very basics of entering a program, compiling it, and running it, as well as provide a simple example C++ program. OK, today we're going to write a ridiculously simple program, one which will print the sentence Hello, world! on the screen. It won't teach you the BeOS API, and it won't even teach you much of C++; it will teach you the basics of how programming works. We'll start off by creating a directory to store our developmental programs in. Open up your home directory in the Tracker, and create a new folder named "development". OK, it's time to write our first program. Using a text editor (StyledEdit is just fine; vi or Gobe Productive will work, too), create a simple plain-text file. No formatting. Add the following to the file:
A Closer Look At The Program There's a complete C++ program. Let's go through it line-by-line. Don't worry if you don't understand everything here; again, the point of this tutorial is more to get you used to the programming process, rather than teach you all the particulars of how this particular program works. The C++ programming specification actually defines only the very basic layout of the language. In order to actually do useful things with C++, you need to add special files called libraries to your program. A library is just a file which defines special commands. Thus, the first line of our program uses the special #include command to pull in a particular library file, namely, iostream. iostream contains commands for input and output streams; thus the name of the file. Then, every program needs a block of code named main. The program always starts executing here, no matter where the main block is located in the program. The int word says that this block will end up with a number value when it's done processing -- don't worry about exactly why we're bothering with this right now. (Actually, the proper term for these blocks is a "function," but we won't worry about that right now either.) OK, then we use a special command that's part of the iostream library; namely, the cout command. cout is used to print things on the screen. We then use the << operator (a kind of command), to say that we're not done cout'ing yet, then we have the special string that we want to print out. This string must be surrounded with double-quote marks; otherwise, the C++ compiler won't know where the string begins and ends. Then we put another << operator, and end the cout statement with the endl operator, which means that this is the end of this line on the screen, which will move the cursor on the screen to the next line. We end the line with a ;, because all complete lines of code (except those starting with #) must end with a ;. Now, remember how I said that this block will end up with a number value when it's done processing? Well, we have to say which number value that is, which is why we finish up this function with return 0;. We end the program with a }, which finishes up the main function. Compiling All right! You've written your first program! Now, save the file as helloworld.cpp, in the development folder of your home folder. OK, now we get to use the C++ compiler. A compiler is a program that takes commands in a particular programming language, and translates them to a whole bunch of commands that the operating system itself can recognize. BeOS can't directly interpret C++ commands; you have to compile each C++ program into a BeOS program. To do this, start up a Terminal window, by clicking on Be -> Applications -> Terminal. This will put you into a Unix-style command-line interface. From the Terminal window, type cd /boot/home/development to move into the directory where you saved your file. Then, compile the program by typing "gcc -lstdc++.r4 helloworld.cpp -o helloworld" (without the quotes). This will start the gcc program (which is the C++ compiler), telling it to load in the stdc++.r4 compiler library (if you're using Release 5, that's OK; the library hasn't changed, so it's still called r4), compiling your helloworld.cpp program, and writing out the actual finished program as helloworld. gcc may come back with a bunch of errors. If you get any errors, go back and make sure that you typed in your code exactly as it's listed above. If you find any errors, change your code, save it, and re-type the line above to re-compile your program. Once you compile without errors, enter the following in the Terminal window: helloworld -- you should immediately get back a line saying Hello, world!. If so, congratulations! You've successfully written, compiled, and executed your first BeOS program! back to Index Variables This tutorial should introduce neophyte programmers to the wonderful world of variables. It's time to start doing vaguely useful things with C++. This tutorial will familiarize you with variables. Variables are simply containers that you can use to store a little data. You can think of a variable as being like a mug; you can fill it with water, or you can put a slip of paper into it, or whatever. However, unlike mugs, variables have to have a particular type. Whenever you create a variable, you have to say exactly what sort of thing you want to put into it. Thus, you could create a variable that will only be able to store one integer number at any given time, or that will only store text. This would be like declaring that your blue Hawaii mug is only to be used for drinking out of; nobody should store pencils in it. The syntax for creating a variable is simple: First comes the type of the variable, followed by the name that you want to use for the variable, followed by a semi-colon (";"). To put a number in the variable, you just type the name of the variable, followed by an equals sign ("="), followed by whatever value you want for the variable, followed by a ;. What types are availalble? Here's a quick list:
A Quick Example Let's write an example before we go any farther. We'll write a simple program that stores a number, then displays it on the screen.
OK. The first thing we did was define a variable, named num, as an int (integer). We then assign the value of 5 to the variable, and then we print out a short message which also prints out whatever's in that variable. Save this program as variables.cpp, then open a Terminal window, go to /boot/home/development, enter gcc -lstdc++.r4 variables.cpp -o variables, and enter variables to run the program. Here's what you should see on the screen: The number is 5 Some Simple Math Operations So, let's review. We've seen how to create variables, and how to assign a value to them, and how to print that out. Not very exciting. But wait! We can do math with our variables! We just use that "variable = value" syntax shown above, and add in mathematical symbols. Thus, you can do this: num = 5 + 9; After that command is executed, num will have the value of 14. This wouldn't be very powerful, except that you can also use variables in there too. Let's write another example.
The first thing you'll notice is a bunch of English sentences, surrounded by /* and */. These are comments, which are completely ignored by the compiler. Comments let you make notes to yourself (and other developers). What I did in the program above is my standard commenting scheme; I start out with a one-line description of this function (that is, main), followed by an outline of what the code does. I then insert each step of the outline wherever the code itself starts performing that step. You'll also notice a special method of declaring our variables; rather than listing the type for each one, we listed the type, then a bunch of variables, separated by commas. This is perfectly legal C++, and is a convenient, quick way to set up a few variables. Unfortunately, this particular syntax makes it difficult to read and find variables, so I don't like to use it often. We then perform a few mathematical functions, and store the results in our four result variables. Finally, we print the results out. Note that we made the cout command work over a number of lines; since the ; doesn't appear until after the last endl operator, the C++ compiler will know to treat it all as one cout command. OK, save the above code as variables1.cpp, and compile it and run it as usual. Here's what you should see: 8 + 3 = 11 8 - 3 - 2 = 3 8 * 3 = 24 8 / 3 = 2.666667 Remember that variables are, well, variable; you can change them at any time during your program. Thus, this is perfectly OK:
String Theory Before we leave, let's write one final example, one that uses chars and strings, just for completeness.
(By the way, variable names can contain any letters (upper- or lower-case), numbers, and underscores, as long as they don't start with a number. Thus, health, a1, and Speaker_System are all valid variable names; 1a, health!, and George'C' are not. Also, variable names are case-sensitive, so health and Health are completely different variables.) The above should be fairly straightforward; only note that we had to include the string library, so that we would have string variables. We just stored the first name, last name, and middle initial of someone into three variables, then stuffed all of that into fullName (formatted with a few spaces), and printed that out. Save this program as variables2.cpp, compile it, and run it. Here's what you should get: The person's name is: George C. Scott back to Index Text Input This tutorial should show neophyte C++ programmers how to read input from the command-line in a Terminal window. How many programs do you have that don't need any input from you at all to do anything useful? Not many, I'll bet. Today we'll look at reading input from the keyboard, so that we can write some really useful C++ programs. Keyboard input is accomplished using cout's lesser sibling, cin. cin uses very similar syntax to cout; you simply put in a list of variables that you want to read in. Let's see an example: Number Input
This program should be fairly obvious. It prompts the user to enter two numbers, then prints out the sum of the two numbers. Notice how cin works: You use the command cin, followed by the >> operator (the reverse of cout's << operator), followed by the name of the variable that you want to put the user's input into. Let's run through the program once. Save the above code as input.cpp, compile it, and run it. The stuff that you're supposed to type in is in bold: Enter any number: 30 Enter another number: 5.5 30 + 5.5 = 35.5 Pretty simple, huh? Let's try this with strings. String Input
Save this as input1.cpp and compile it. Let's run through it once. What is your name? Lain Hello, Lain. back to Index Arrays This tutorial should introduce the beginning C++ programmer to the concept of arrays. Why on earth would you need an array? Learn about 'em first, and then judge. An array is basically just a list of variables. Rather than naming each variable in the list, the entire list has a name, and each variable in the list has a number, or index. All C++ arrays start their arrays with index 0, and count up from there. Thus, the first item (a.k.a. element) in the array has index 0, the second has index 1, the third has index 2, etc. Let's see how to create an array before we go any further. int list[10]; The above code creates an array of integers, with 10 elements in it. However, because all arrays start at 0, these elements are numbered 0 through 9. Confusing, I know, but you'll get used to it. Let's set the value of and access one element in that array: int list[10]; list[0] = 0; if( list[0] == 0 ) list[1] = 1; As you can see, accessing individual elements of an array is easy; just specify which index you want, surrounded by [ ] brackets. The really useful part about arrays is that you can substitute a variable for that index. Thus, this is OK: int array[10]; int i; i = 0; array[i] = 0; OK, good enough. Let's write something fairly useful with arrays. NOTE: The following program uses a loop, which is something that is explained fully in a later tutorial. Suffice to say that a loop does something over and over again, until a certain condition is met.
If you were to run this, here's what you'd see: How many numbers do you want to sum? 5 Enter #1: 12 Enter #2: 3.5 Enter #3: 4.99 Enter #4: 20.95 Enter #5: 4 The sum of the above nubmers is: 82.44 back to Index If Statements This tutorial should introduce beginning programmers to the very basics of conditional programming, using if() statements. Thus far, our programs have been pretty linear; they start, they do some stuff, and then they stop. Our programs haven't made any sort of decision-making. They're about to. Let's look at the wonderful world of if() statements. if() statements are special commands that make a comparison (or a bunch of comparisons), and make a yes/no decision based on those comparisons. There are no "maybe's" when you're using if; the result is either true or false. The syntax of the if() statement looks a lot like the syntax of the main() function. You start out with if, followed by an opening parentheses ("("), followed by an expression (more on that in a moment), then a closing parentheses (")"), an opening curly bracket ("{"), the code that you want to execute if the expression is true, and finally, a closing curly bracket ("}}). Here's a quick example:
The above declares a variable named i, puts 5 into it, then sees if the value of i is greater than 1. If i is greater than 1, then i is set to 1. Simple enough. Yes, it's silly; we'll see some more useful examples in a little bit. Note that you can have any number of spaces between the if, the parentheses, and the values of the expression (or no spaces, if you prefer); the following is perfectly legal C++:
This is OK, too:
I just prefer my own little syntax ("if( i > 1 )"); you can format it however you want. Expressionism So, what are these expressions? An expression is composed of one or more comparisons (e.g., "health < 100", separated by Boolean operators. Let's see exactly what I mean. Comparisons are simply a matter of comparing one thing to another; is the value of this variable greater than this number? Is the value of this variable equal to the value of this other variable? Here's a quick list of the operators that you can use:
OK, that makes sense. What about these Boolean operators? Well, they're a way to put a bunch of comparisons all in one if statement. For example, what if your program had to check to see if a variable was within a valid range, say from 1 to 100? You could write it like this:
Fortunately, there's an easier way! You can combine conditions together using Boolean operators. Here are the two main Boolean operators:
So, we could compress the above code like this:
What Else? OK, what happens if you want to do something when the condition is not satisfied? For example, let's say we want to protect our program with a password prompt. If the user enters the correct password, they should be able to use the rest of the program, but if they don't enter the right password, they should get an error message. We can do this with the secret other half of the if() statement; the else clause. Let's just see it in action:
Save and compile this program. Note that you should not name the program if.cpp, as the compiled program will be called if, which is a special command in the Terminal. It's perfectly fine to call it if1.cpp. In any event, let's run the program, giving it the correct password: Login: joshua Greetings, Professor Falken. Shall we play a game? Now we'll run it again, giving it the wrong password: Login: james Access denied. A Complete Example Let's write a simple number guessing game. It will ask the player for a number, which it will then perform a series of calculations on, to get out of it a number between 1 and 10. The user can then guess that number.
OK, let's give the game a whirl. Save the above source code in a file, compile it, and execute it as usual. Guess a number from 1 to 10! Impress your friends! Enter any integer number: 90 The number I picked is no more than five. Guess the number (it's between 1 and 10): 2 Sorry! I had picked the number 3 back to Index Loops This tutorial should introduce begnner C++ programmers to the concept of programming loops. Today we'll talk about making your program do something over and over again. Welcome to the wonderful world of loops. There are two main kinds of loops in C++; while() loops and for() loops. while() loops are general-purpose loops, whereas for() loops can be seen as a special kind of while() loop that is especially for counting. You'll see what I mean. While You're At It OK, we have this particular loop called while, which will make your program do the same thing over and over. The first thing you should worry about is how to stop the loop. That's easy; you put an expression within the parentheses of the while() loop; say, i == 10. Once that expression is false, the while() loop will stop. Let's see an example.
Let's take this step-by-step.
Save the above program as while..cpp, compile it, and execute it. Here's what you should see: The counter is 1 The counter is 2 The counter is 3 All done. A while() loop can have any expression you'd like, including Boolean expressions. The following is a perfectly valid while() loop expression:
For He's A Jolly Good Loop Since a lot of loops are concerned with counting in one way or another (do this ten times, or go through all of the elements in a list, or whatever), a special kind of loop was developed, one that is especially for counting. This new kind of loop is called a for() loop. It looks like a while() loop, but the expression has three parts:
Let's re-write the above example (while.cpp) to use a for() loop.
See how much shorter that is than using a while() loop? Of course, the for() loop has its disadvantages; it's meant for counting. Complex loops (like example with name == "George" and so forth) deserve the while() command. A Fun Example Let's take the number-guessing game from the "If Statements" tutorial, and expand it using loops. We'll make it so that the game will continue until the user guesses the correct answer. While (ha!) we're at it, after every incorrect guess, we'll tell the user if that guess was too low or too high.
Now save this as guess-a-num.cpp, compile it, and execute it. Here's a sample run: Guess a number from 1 to 10! Impress your friends! Enter any integer number: 20 The number I picked is more than five. Guess the number (it's between 1 and 10): 7 Nope; too low. Guess the number (it's between 1 and 10): 9 Nope; too low. Guess the number (it's between 1 and 10): 10 Congratulations! You were right! back to Index Functions This tutorial should introduce beginningC++ programmers to the ins and outs of functions, including defining and calling them. One of the biggest problems in programming today is code maintenance. The larger your program becomes, the harder it is to track down errors in the program. Even if a program compiles, that doesn't mean that it will work properly. So, today we'll talk about splitting programs up into separate functions. Functions are just named chunks of code, with a few special properties:
The good news is that you've been using functions all this time! You know main()? That's a function! Let's look at how we use main():
OK, the above code says that main() returns an integer value (int); this is the return type of the function. The list of parameters is included in the parentheses; in this case, we use the keyword void to say that we don't want any parameters. We could also use void for the return type, to say that the function won't return anything. We then put the contents of the function between a set of { } brackets. The last line in the function specifies the integer value that we want to return (0, in this case). Parameters Let's take a closer look at parameters; they are what gives functions most of their power. But first, we need to talk about local variables. A local variable is a variable that only exists for part of a program's execution. Once execution leaves that part of the code, the local variable ceases to exist; if that piece of code is run later on, the local variable is re-created. We can create variables that are local to a particular function. Thus, whenever that function is run, the variables will be created, and when the function finishes running, the variables will be cleaned up. OK, so what are parameters? A parameter is kind of like a special local variable, one that is given a value by code that asks to run the function. This lets you send data into a function. Confused? Let's see an example.
As you can see from this code, we create a function named square(), that has one parameter: in, which is a float (floating-point number). square() returns the parameter times itself; or the square of the paremeter. The main program asks for a number, then calls the square function, giving it the number that the user entered, and printing out whatever is returned. When square() is called, its in parameter is given the value of the variable that was used to call the function. So, if num was 2.3, then when square() was called, in would be given the value 2.3. Let's run this program, and see what we get. Enter any number: 3 3 squared is 9 What about a function with no parameters? That's easy. Let's augment the above program to include instructions.
As you can see, this program now calls the print_instructions() function at the beginning of the program. print_instructions() then prints out instructions for using the program. It takes no parameters, and doesn't return anything. Let's run it and see what we get: Square Generator This program displays the square of any number. Enter any number at the prompt (e.g., 4.5), and the program will print its square. Enter any number: 5 5 squared is 25 back to Index Text Adventure This tutorial brings together all of the concepts used in the previous tutorials, to create one non-trivial program. Now that we've seen different aspects of the C++ language, let's use all of them to write one complete program. This will demonstrate all of these elements of C++ working in concert to present a unified program. This tutorial presents a simple text adventure, or a text-based game that requires the user to navigate through an environment. The player will be able to move from room to room and pick up objects. Let's start out by defining what will happen in the program: Loop until we want to quit. The Travel Table Before we begin, let's look at a certain data structure which we'll use to keep track of the environment. This data structure is called a Travel Table. A Travel Table is a two-dimensional array (e.g., a grid of variables), which stores information on all of the rooms in the game. Rather than try to explain it, let me chart out a simple travel table:
Each row in this table corresponds to a room in the game. Each column represents a direction that the user can move, to leave that room. Column 1, thus, stores the number of the room to the north of any given room, while column 2 stores the number of the room to the east, column 3 stores the number of the room to the south, and so forth. Column 0 -- and row 0, for that matter -- are ignored. An entry of 0 indicates that the user can't move in that direction. So, look at the table above, at row 1. We see that all of the entries are 0, except for column 1, which has a 2 in it. This tells us that, from room 1, we can go north to room 2. Looking at row 2, we see that we can go south from room 2, which will take us to room 1. The Code Itself All righty. Let's start writing the program.
All we've done here is defined a set of variables, one specifying whether the user has picked up a torch, one specifying whether the user has picked up a key, one specifying which room the user is in, one specifying whether the user wants to quit yet, and one to hold whatever command the user enters. We've also started a loop, which will loop as long as the iQuit variable is 0, and the user hasn't entered room 8. When the user wants to quit (or is killed), we'll set iQuit to something other than 0. OK, let's continue. We'll print out the room's description.
What's this? We've called a function that we haven't even seen yet; what is this displayRoom()? Don't worry; we'll write that function later. For now, we're just going to leave it as-is. Let's go on and tell the player what s/he has in his inventory.
OK, we start out by printing the phrase "You are carrying". Then, if the user is carrying the torch, we print " a torch". If the user is carrying a key, we print " a key". If the user is carrying neither (both boolHasTorch and boolHasKey are false), we print " nothing", then finish up by printing a single period, and end the line. All right, now let's ask the player for some input.
Nothing sepcial here. We print out a prompt, then read the input into the cCommand variable, which holds a single character. Now we see what the player told us to do.
All right. If the user typed a lower-case or upper-case N, we call a function called canMoveFromRoom(), and send it the current room number (which is stored in iRoom), and a 1. The number 1 corresponds to the direction to look in; north corresponds to 1. If that function returns true, then we set the room to be equal to whatever is to the north of the current room, according to the Travel Table. Note that we haven't defined canMoveFromRoom() yet. Don't worry; we'll write that later. Let's do the same thing for east, south, and west.
As you can see, the code is the same for the other directions; we just have to use the appropriate direction number. OK, now we'll code what happens when the user wants to pick up something.
In this case, we make use of the fifth column of the travel table. This fifth column stores information about anything that happens to be in the room. If this column is 0, there's nothing in the room; it it's 1, there's a torch, and if 2, there's a key. So. If the fifth column of the travel table equals 1, we set our boolHasTorch variable to true (effectively picking up the torch), we print out a message saying that the player has picked up the torch, and we set that fifth column of the travel table to 0 for the current room. We do the same thing for the key. If that fifth column is not 1 or 2, then it must be 0, which means there's nothing in the room. So, in that case, we print out a message to that effect. Getting close. Let's finish up main().
All right; that's it for main(). We print out a list of commands if the user asks for help, and we quit when the user asks to quit. Laying Out the World Scroll back up to the top of your file. Let's write up the travel table.
This is our travel table. You can try to navigate through it mentally if you'd like. Describing Our World We still have two functions to write; displayRoom() and canMoveFromRoom(). Let's start with displayRoom().
At the beginning of the function, we have a comment block that explains what the function will do. You can see there a sneaky little enhancement to the game: If the player doesn't have the torch, and the room doesn't have a torch, then we don't let the user see what's in the room. This might surprise the player the first time s/he leaves the room that has the torch in it! Fortunately, that code is very simple:
Simple enough. Now let's see what happens if the player has a torch handy.
This code almost couldn't be simpler. If the room is 1, we print out the description of room 1. The descriptions of rooms 2 through 7 will be just as simple.
That was simple enough; for each different room number, print out a different description. Now we'll print out information depending on what's in the room.
OK. If the fifth column (the one containing room contents information) is 1, then we say that there's a torch in this room. If that column is 2, we describe a small key in the room. We have one more function to write, the one which returns true if the player can move out of a given room, and false otherwise. Here comes the code:
I think the above code should make sense based on the comments. Conclusion And...we're done! That's all of the code in our example. Check out the full source listing, and run the program yourself. Here are a few enhancements you can try on your own:
back to Index BeOS API Overview This tutorial should familiarize the beginner programmer with the structure and relationships of the BeOS API. The API The BeOS API (Application Programming Interface) is a set of classes that Be has created so that developers can use standardized BeOS GUI elements like windows, alert boxes, buttons, and so forth in their own BeOS programs. OK, so Be has got a whole bunch of classes, each one corresponding to an individual GUI widget. There's BWindow, which creates a window, BCheckBox, which displays a check box and a label, BButton, which displays a pushbutton, and so forth. All of these classes are declared in .h files that reside in the /boot/beos/develop directory. Thus, there's a Window.h file, a CheckBox.h file, a Button.h file, etc. Thus, to use any of these classes, all that a developer need do is #include the appropriate .h file. So if, for example, I wanted my BeOS program to have a window, a push button, and a text view, I'd have something like this at the beginning of my program:
However, there has to be some way for programmers to control the behavior of their widgets; when the user clicks on a button, the programmer needs to be able to specify what happens next. This is accomplished in the following way: The programmer derives his or her own classes from the classes provided in the .h files. He or she then implements (or overrides) whatever functions are provided for that class, to control the widget. If our programmer wanted to have a button which does something once the user has clicked on it, s/he would create a class called, for example, MyButton which derives from BButton, then override the SetValue() function, and write the code to do whatever needs to be done when the button is activated. OK, perhaps that's sort of unclear. Let's see a simple example:
So we've just created our own version of the BButton, but we'll be able to write our own SetValue() function to do whatever we want. That SetValue() function will be called by the BeOS whenever a user clicks on our button. Compilers BeOS comes with BeIDE, an integrated development environment that you can use to develop your own applications. Just fire it up from the Applications menu, and it should be fairly easy to figure out. BeOS for Intel x86 also comes with gcc, a free text-based compiler. If you're used to Unix-based compiling, you can compile with gcc and Makefiles. See a Unix book (such as the excellent Unix in a Nutshell from O'Reilly) for more information. Actually, the above is a bit misleading -- as it turns out, the Intel version of BeIDE is based around gcc, and actually calls gcc when it compiles. I'd go into more detail on this, but that's about it. If you're unclear on any of this, or just want to get into the specifics of coding, go to A Dissection of Hello World. back to Index A Dissection of Hello World This tutorial should familiarize the beginner programmer with the programming resources available for the BeOS, the overall structure of a BeOS program, and some basic BeOS programming concepts. Resources - CodeWarrior, or, How To Make Threads And Influence Programs So you want to program for the BeOS. Well, you're in luck: BeOS automatically installs a GUI C++ compiler along with the OS, so you can start programming in C++ for the BeOS right away. It's a limited version of Metrowerks CodeWarrior; it doesn't do everything, but, hey, it's free. Before we start learning BeOS C++, let's get introduced to CodeWarrior. Click on Be -> Apps -> BeIDE to bring up CodeWarrior. Now click on File -> New Project to create a new project. A window will pop up, letting you select the Stationery to use as an underlying structure for your program. Expand the x86 or PPC item, and select "Be" (if you don't, you won't be able to compile your code into a BeOS application). Click OK and save your new project as a ".proj" file. Now you have an empty project. Click File -> New Text to create a new .cpp file in this project. Immediately save it as a .cpp file, then go back into the project window, click Project -> Add Files, and add your new .ccp file into the project. Voila! You now have a complete C++ project to work with. Structure of a BeOS Program, or, How It All Hangs Together BeOS programs are a bit complicated in structure, but sensical. Because the BeOS system is so aggressively object-oriented, things tend to be compartmentalized more than in other systems. All window-based BeOS programs (and by "all" I mean "all except for rare, weird cases") have three basic components: Application, Window, and View. The Application object is the main control engine of the program. You create a Window for every BeOS window you want to have in your application. Windows don't do much GUI work in and of themselves, however, so every functional area in your window has to be created as a new View (a menubar is a View, a text input field is a View, etc.). OK, now that you're familiar with the basic concepts, let's write a Hello World app. Each piece of code will go in different places, so stay sharp! Hello World - the Application, or, How To Write A Job Application This section will go sequentially in the file, in the order in which it appears here. We're starting off with the application object, which controls the rest of the app.
This will be used to create the main Application object, which will in turn create the program's window. Note the creation of a pointer to a MainWin, which will be our main Window object.
All right, this creates our application. First off, it calls the BApplication's constructor, and passes that weird string, "application/x-vnd.Tutorial-HelloWorld". That's the unique signature of this particular application, which BeOS will use to keep track of it. You can put anything after the "x-vnd."; the convention is company name followed by product name, without spaces. We want to create a Window object, and to do that we have to define its position. We create a BRect (rectangle object), which will hold the size of the main window, and set the size of that rectangle. Finally, we actually create the new MainWin, myMainWindow, passing it that rectangle. MainWin is the program's Window object, which we'll look at next. Hello World - the Window, or, No, Not That Kind Of Windows OK, now that we've seen what an application looks like, add in the following code above the HelloWorldApp code.
Here we have a simple Window class. The only thing of real interest is the QuitRequested function, which is called when the user closes the Window. Let's look at the constructor for this Window. Put this code right after the code above.
All right, we call the default constructor, then add a new MainView as a View on this window, filling this entire Window with it (with the Bounds()). Then we call Show(); to actually display the View. Now we'll look at the QuitRequested function, which should go just below the code we just entered.
Hmmmmm, not very exciting. be_app is an automatically-generated pointer to the current application, so we use that for convenience. All right, then, we're sending a message to the current application, saying that a quit was requested. Then we return true, indicating that we can indeed close down the Window. We'd return false if, for example, the user had unsaved changes to his/her work, and we wanted to prevent the user from exiting the program. Hello World - the View, or, What A Nice View You Have From This Window Now we've looked at the Application and the Window, let's look at the View itself. This should go above the code we've already entered (at the top of the file).
Again, nothing surprising. We do have a Draw function, which is not unlike Java's paint() function. This next chunk of code should go below the code we just put in.
Boy, this is boring.
All right, now we move the current cursor position to 20,50 within the View (e.g., 20 pixels across from the left edge of the View, and 50 pixels down from the top edge of the View), and draw "Hello, World!" there. Finishing up Hello World, or, Hello World Says Hello OK, now all we have remaining to add are main() and the header files. Here's main(), which should go at the very bottom of the file.
This is straightforward C++; create the Application object, then run it. It'll get deleted automatically; no need for delete. The Application object will create the Window, which will create the View. The header files are easy enough, and go at the very tip-top of the file.
All right! We've got a complete BeOS Hello World program coded! Here's how the code should look:
To compile the program, select Project -> Make, or hit ALT-M. It should compile nicely. To run it, you'll have to open a Tracker window inside the folder that was created for your project, and double-click on the file named "BeApp". Optional Modifications
back to Index Order Calculator This tutorial should familiarize the beginner programmer with the structure and relationships in the GUI elements of a BeOS program. What It's All About OK, this tutorial will go through a simple BeOS program that actually uses GUI elements (e.g. buttons and text controls). Once you're done with this, you should understand how GUI elements interact. We'll only have one window, so use the basic BApplication and BWindow structure seen in the A Dissection of Hello World or Raycaster tutorials. All we'll worry about is the BView, and its offspring. The premise is that we've been contracted to write an order cost calculator for a mail-order company. They want a program that will let them enter the quantity of each item a customer is ordering, then click a button to have the whole thing totalled, along with sales tax. Fortunately, they're a small company, and only sell four items: a regular keyboard, an ergonomic keyboard, a mouse, and a trackball. Setting Up The BView Let's look at the code that declares our BView.
As you can see, all we've done is declare the constructor and six different controls (plus a special function called MessageReceived(), but we'll get into that later). The BTextControls will display a line for each item (e.g., "Ergonomic Keyboard, $89"), and a text input box where the user can enter in the quantity of each item. sviewSalesTax will just display the sales tax, and btnProcess will perform the appropriate calculations. OK, let's look at that constructor.
That's it! We just set up the appropriate rectangles, then construct new objects for each of our controls, then add them to the view with AddChild(). There is one weird thing, though: that little BMessage. BMessages are used to send data from one part of the system to another. They're pretty handy. In this case, we've created a BMessage with a value of "1" (BMessages can have all sorts of data), and stuffed it into btnProcess. When btnProcess is clicked, it will send a copy of the BMessage we constructed to btnProcess' parent window. The upshot is that we'll have to look for that message in the BWindow. Fortunately, BWindows already have a function that takes care of that, namely, MessageRecieved().
OK, we start out by declaring a few variables, then setting up a case statement based on the incoming BMessage's what member variable. Once we see that the message contains the "1" that we set up for the Process button, we start calculating the total. That's done by converting the values in each of the text boxes to ints, stuffing them in variables, then using those values to calculate a subtotal. Once we have the subtotal, we use sprintf() to create a string starting with "Total: " and ending with the sutotal, plus sales tax. That string is then displayed as the msesage in a new BAlert, which is a simple alert box that is popped up onto the screen as soon as its Go() function is executed. Once that's taken care of, we're done! The last part of the above code (the "default:") takes care of all of the other messages that might be sent to your window; they're sent to the BWindow version of MessageReceived(), so that they can be properly processed. Optional Modifications
back to Index E-Mail Checker This tutorial should familiarize BeOS programmers with network-based programming, particularly with e-mail applications. Want to write your own networking apps? The BeOS provides a dead-easy way to do it: Nettle, the new BeOS networking API. Well, it's "new" in that it only popped up in R4.5. Anyway, with the use of one class, you can easily connect to a server, send commands to that server, and receive responses to your commands. To illustrate how all this is possible, we're going to write a simple application that connects to a mail server (any server using the POP protocol, that is), logs into it, asks for the number of e-mails for that account, and displays the result on the screen. The layout of this window will be simple. There will be three text controls, where the user can enter in the name of the POP mail server, the user's e-mail username, and the user's e-mail password. There will also be a string view which will display the number of messages. Finally, there will be a "Check" button which the user will click to connect to the server. Let's begin! Some Simple Set-up
Now, note that we've set up a constant, CHECK_EMAIL. This constant will be used to notify the window when the user has clicked on the "Check" button. Let's go on to the BView's constuctor.
OK, all we have here is the constructor, and a function called checkEMail(). This function will be called when the BWindow has been notified that the user has clicked on the "Check" button. Let's finish up the BView's declaration, by declaring a few controls.
We have three sorts of controls here. Let's look at each type.
With me so far? All right, let's write the BView's constructor. We won't do anything fancy; we'll just create the controls listed above.
Let's stop for a moment and review what we've done here. The r.Set line sets up a rectangle, which will be used to define the size of the control. After that's defined, we actually create the control itself, passing it the rectangle, an internal name, a string to display next to the text box, a value for the text in the text box (in this case, we want the text box to be empty, so we pass "", an empty string), and a BMessage that will be sent to this BView's parent when the user interacts with the text box. Since we don't care what happens exactly when the user changes the server name, we pass in NULL, so that no message will be sent. Finally, we add the newly-created control to the view. Now let's do the same thing for the rest of the controls.
The "Check e-mail" button does something a little different, so let's look at that. We pass in the rectangle (r), the internal name for the control, the label that will be displayed on the button ("Check"), and a newly-created BMessage. We didn't want to be notified when the user interacted with the text controls, but we do care what happens when the user clicks on this button. Thus, we create a new BMessage, giving it the constant that we've defined. A copy of that BMessage will be sent to the window every time the user clicks on the "Check" button. OK, let's finish up the initialization of these controls.
Very good. We've created all of our controls, and placed them on-screen. Next, we'll write the function that actually connects to and communicates with the server. This is the heart of our tutorial. The Heart of the Matter
This is my "road map" of what the function will do. It's handy to write this out beforehand, so that you have a good idea of how to organize the function.
BNetEndpoints are used to connect to, and communicate with, other servers. They can also be used as servers, but let's not get into that. Point being, we're setting up a BNetEndpoint to act as our liason with the POP server. Fair enough. Next, we'll set up some miscellaneous variables, and do a bit of error-checking.
Note that we have one particularly unusual variable; strCRLF. This variable holds two special characters, carriage-return (ASCII code 13), and linefeed (ASCII code 10). Every line of text sent to and received from a POP server, including commands, must end with these two characters, in that order (carriage-return, then linefeed). If the user hasn't filled out the Server, Username, and Password fields, then there's not much point in trying to connect to the server, so we immediately return a value of false.
All right, what's happening here? First off, we create a new BNetEndpoint. Then we tell it to connect to whatever's specified in the Server control, on port 110. All POP servers communicate on port 110. Then, we check to see if the connection was successful. If it wasn't, we immediately return false. OK. At this point, The BNetEndpoint has successfully created a connection to the POP server. Good. Let's start chatting with the server.
As soon as you connect to a POP server, the server sends a message, along the lines of "+OK mail.everyone.net POP3 server ready Tue, 2 Dec 1999 03:45:29 GMT". This message varies from server to server, but if you're successful, the first three characters of that message will be "+OK". So, we check those characters, and if they're not "+OK", then we immediately return false. Actually, for most commands that you send to a server, the response will start with "+OK" if your command was successful. You'll see this used a lot as we cut our way further into the jungle. Next, we have to send the username.
Now we stuff a string with "USER xxxxxxxx", where "xxxxxxxx" is the username entered in the Username text box. Then we send that string to the server (note how we added the CRLF string to the end of our command), and receive the response. If the response doesn't begin with "+OK", then something went wrong, so we immediately return false. Next, we'll do the same thing for the user's password.
You'll notice that this section of code and the section above it look very similar. The only change is that we send PASS instead of USER, and of course we use the text in the Password text box. As usual, if we don't get a "+OK" back from the server, we return false. If we've made it this far, then we've logged into the server successfully. Now we have to ask the server for the number of e-mails waiting.
We send the message "STAT". In case you're wondering, the 6 is the length of the message to send (in this case, 4 characters for "STAT", plus 2 characters for the carriage-return and linefeed). Then we receive a response from the server, and stuff it into a string variable.
Do I even need to explain this? Only this time, since we have an actual string variable to work with, we can use the convenient substr function (0 for the character to start at, and 3 for the length of the string to retrieve). One last thing to do with the server; let's close the connection.
The Fruit of our Labors OK, assuming that we got a "+OK" from the server, we need to actually parse the response to find out how many messages are available. In response to a "STAT" command, the server responds with the number of e-mails stored, followed by a space, followed by the number of bytes total in all of the stored e-mails. For example, after sending our "STAT" command, we might get back the string "+OK 2 2701", which means that we have two e-mails stored on the server, and that they take up a total of 2,701 bytes (where a byte is made up of 8 bits). So, we have to pull that "2", or whatever, out of there. The problem is that we have to know the length of the string that we want to pull out. We'll do that by looking for the space that comes right after the number.
So, we look at the character at position 5 in the string. The first position is 0, so in the string "+OK 2 2701", character 5 is here (where the underscore is): "+OK 2_2701". If that's a space (and in our example, it is), then we pull out the string starting at character 4, of length 1. If, however, there's no space at that position, then we know that we have at least 10 messages. Thus, we look at the character at position 6; if it's a space, then we pull out the string starting at character 4, of length 2. And so forth. The resulting string is placed in the strNumEMails variable. Do note that the above code will not react properly if the user has more than 9,999 messages. Most e-mail accounts fill up when they have only a few thousand e-mails in them, though, so this shouldn't be too much of a problem. Feel free to extend the code if so desired. Let's review. We've connected to the server, we've asked for the STAT, we've received the response, and we've placed the number of messages in that response in a convenient string variable. Now we'll display that number on the window.
We construct a string that uses the number we've just found. In our example, this string would say "You have 2 e-mail(s)." We then set the Result string view to display that string. That's it for this function! We can now return true and end the function, since we've done our job. Now all we have to do is set up the window. Window Dressing This will be fairly straightforward. We'll set up the window in a very boring manner, and then make sure that checkEMail() gets called when the user clicks on the "Check" button. Let's start with the BWindow's constructor.
OK, nothing too surprising here. We create a new view, add it to the window, make sure the view has focus, and then show the window. Almost done! Now we have to set up the window so that it does the right thing when the user clicks on the "Check" button.
Remember waaaay back when we were creating btnCheck? Remember how we created a BMessage, which used the CHECK_EMAIL constant? Well, when the user clicks on that button, an exact copy of that BMessage will be sent to the BWindow's MessageReceived function. The code we've just written looks for an incoming BMessage that uses that constant, and if it's found, calls the view's checkEMail() function, which will connect to the server and check for "+OK" a bunch of times. Only a little more clean-up to do.
If we get any message other than CHECK_EMAIL, we don't want to handle it. However, it may be an important BWindow-related message, so we pass it to BWindow's MessageReceived function, which can do whatever it wants with that message. That's it! We're done. The only thing left is to create a BApplication object and create our WinEMail from it, which I'm going to leave to you for the time being. Check out A Dissection of Hello World for details if you need them. back to Index Raycaster This tutorial should familiarize the beginner BeOS programmer with the principles of raycasting, and in particular what it looks like as a BeOS program. Casting Out A Ray How do Quake and DOOM display a 3D environment? Well, I'm not going to explain that here. :-) However, I will explain the basic way in which those games draw the world around the player. The technique these programs use is called raycasting. True to its name, the idea behind raycasting is to cast out rays from the player. The program starts out by keeping a map of the environment. Then, whenever it wants to redraw the world, it calculates a ray moving out from the leftmost edge of the player's vision, and maps the end of that ray to the map. When the end of the ray hits a wall, the program draws a wall segment, at a height proportional to the length of the ray, at the leftmost edge of the screen. The program then casts out another ray, just to the right of the previous one, and does the same thing, drawing the next segment just to the right of the previous one. It continues for the entire visible area, drawing wall segments through the entire visible range. You might want to try this on paper, to see how it works. To show exactly how that's done, we'll write some code to do this on a BView. The BApplication and BWindow should be created normally. Setting Up
First we have the public functions; all of them are BView functions that we're overriding. There's the constructor, which will set up the environment, KeyDown(), a hook function which gets called whenever the user hits a keyboard key, and Draw(), another hook function that gets called whenever the view needs to redraw itself. Then comes a private function, DrawWorld(). Predictably enough, this function does the job of actually raytracing out the current view of the world. And finally, we see some private variables. player_x, player_y, and player_angle record the player's current position. The wall_grid stores a map of the environment, where each element of the array is a wall section. The last two arrays store the values of sine and cosine functions for certain values. Both sine and cosine are used a lot in our raycasting, so they're stored in these arrays beforehand, then these arrays are used when raytracing, rather than having to re-calculate sine and cosine all the time. Let's start on the constructor for this view.
OK, in this section, basically all we do is set up the BView itself, and calculate the sine and cosine functions. This pre-calculation will save ourselves a lot of processing later on.
The main point of this section of code is to initialize the walls of the world. It starts out by creating walls around the border of the world, then adds two further runs of walls in the middle of the map. Note that the world must have walls all around it! There's no code to handle a section of world without a boundary wall being somewhere in the distance. Once the walls have been set to our satisfaction, the player's initial position is set up, using the player_x, player_y, and player_angle variables. Finally, the code draws the world on the screen. Capturing Keyboard Data
The first thing to note here is the two parameters sent to KeyDown(), namely a char * named bytes, and an int32 named numBytes. Conveniently enough, bytes contains the key that the user has pressed. Sometimes more than one byte is needed to represent the keypress; thus the char * and numBytes. But fortunately, BeOS provides a set of pre-defined codes we can test bytes against. Thus, the first section of code in this function tests for B_LEFT_ARROW and B_RIGHT_ARROW. In either case, player_angle is modified appropriately, and the world is redrawn on the screen.
This is the code responsible for moving the player forward. First, the player's current position is backed up in old_player_x and old_player_y. player_x and player_y are then changed based on the player's current viewing angle, and those stored sine and cosine functions. Why is 39 added to player_angle? Because when the world is drawn in DrawWorld(), it's drawn from player_angle across to player_angle + 79. This keeps the DrawWorld() code from having to worry about negative angles. The drawback is that the middle of the viewable world (right in front of the player) is actually halfway between those two angles, at player_angle + 39. After player_x and player_y are updated, the code checks to see if that would put the player in the middle of a wall segment. If so, the backup values are put back into the player's position variables. If not, we're OK and the world is redrawn. The next piece of code will deal with moving backwards. It's just the same as moving forward (except for the exact calculations of player_x and player_y, of course), so I won't comment on it.
And now the end of the KeyDown() code, which deals with [T] and [ESC].
This bit of code takes care of [T] for teleport, and [ESC] which quits the program. If the player hits [T] (upper- or lower-case), the appropriate variables get reset to their original values, and the world is redrawn thanks to DrawWorld(). If the user hits [ESC], the program exits. Simple enough. Now, to draw up the appropriate stuff on the view. Setting Up The View Now we'll implement Draw(), which is called whenever the view needs to redraw itself. This happens when the view is first put on the screen, when another window moves over the view, etc.
The only really important part of this program is the call to DrawWorld(), which re-displays the world. A few strings are drawn on the screen, too, just for the heck of it, and a "border" line is drawn between the strings and the displayed world. Note that Draw() gets one parameter, updateRect. This is the part of the screen that has to be updated. We could test against it and only redraw the sections of the screen that are within that rectangle, but this whole drawing process is quick enough that it's not worth the extra effort IMO. Now for the meat. Casting Out Rays OK, here's the heart of our program: the actual raycasting code, in DrawWorld(). We'll go through it bit by bit.
First, we set up some variables: the x position, y position, angle, and length of the ray that will be shooting out, the amount the ray moves in the x and y directions, and some other temporary variables. We also draw a big filled rectangle in the current LOW color (e.g., the background color: white), to erase whatever was displayed previously.
OK, we're starting a loop that will go from player_angle to player_angle + 79, or 80 degrees of vision. This is somewhat less than normal human vision, for what it's worth. We then calculate the amount to move in the x and y directions, and set up the ray: it starts at the player's position, with a length of 0. The wall variable keeps track of whether the ray has hit a wall yet. As long as wall equals 0, the ray hasn't hit a wall.
Now the ray is actually being sent out. The code updates the position of the end of the ray based on the increment variables, and increases its length. The position of the end of the ray is then mapped to wall_grid, and wall is updated based on what's there. As soon as the end of the ray is on a non-zero entry in wall_grid (e.g., a wall), this loop will exit. Next, the encoutered wall segment is calculated and drawn.
Now that the ray has hit a wall (we wouldn't have left the while() loop if it hadn't), the code will calculate the height of the wall segment it's about to draw, based on the length of the ray. This gets stored in wall_height. Then the X variable is filled with the position on the screen that's needed to draw that segment, and that segment is drawn with a call to FillRect(). And that's the end of the loop, and the DrawWorld() function! Pretty simple, isn't it? The only thing left to do is write up the BApplication and BWindow. Look at the entire program. back to Index |