A set of tutorials for the beginning Be programmer
by Brent P. Newhall / 2000

System: BeOS  

  • Introduction to C++
    • Hello World, Text Version - The simplest C++ program around, with full instructions on how to compile and execute it. If you're not sure where to start, start here.
    • Variables - An introduction to the uses of variables, building on the above tutorial.
    • Text Input - How to read in input in a text-based C++ program. Builds on the above tutorial.
    • Arrays - Take variables to the next level, with lists of variables.
    • If Statements - Introduce conditions into your programs.
    • Loops - Let your program do something again and again and again and agian and again....
    • Functions - Split your program up into logical chunks.
    • Text Adventure - See all of the techniques and features described above, used in one complete game.
  • BeOS API Overview - A quick introduction to how BeOS programming works on a conceptual level.
  • A Dissection of Hello World - An introduction to programming in the BeOS, based on Eric Shepherd's Hello World tutorial.
  • Order Calculator - A straightforward GUI application, to teach the basics of a GUI program (text input boxes, buttons, etc.).
  • E-Mail Checker - A program that uses BeOS' Nettle API to talk to an e-mail server.
  • Raycaster - A simple program that illustrates the basic concepts behind Wolfenstein 3D and DOOM. Walk around a (very boring) 3D world on the BeOS!


  • RED = Highlighted   |    BLUE = New Code   |    GREEN = Output


     

    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:

    #include <iostream>

    int main( void )
    {
       cout << "Hello, world!" << endl;
       return 0;
    }


    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:

    Type Examples Notes
    int 4, 102 Integer -- a number with no decimal or fraction part
    float 3.8, 40.10039, 6 Floating-point number -- any sort of real number, with or without a decimal place
    char 'a', '%', '2' Character -- a single character
    string "Hello", "5" String -- contains text; must use #include <string>


    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.

    #include <iostream>

    int main( void )
    {
       int num;
       num = 5;
       cout << "The number is " << num << endl;
       return 0;
    }


    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.

    #include <iostream>

    int main( void )
    {
       /* ----- Performs various mathematical operations on 8 and 3.

         Perform the math.
         Print out the result. */

       float number1;
       float number2;
       float add_result, subtract_result, multiply_result, divide_result;
       number1 = 8;
       number2 = 3;

       /* Perform the math. */
       add_result = number1 + number2;
       subtract_result = number1 - number2 - 2;
       multiply_result = number1 * number2;
       divide_result = number1 / number2;

       /* Print out the result. */
       cout << number1 << " + " << number2
         << " = " << add_result << endl
         << number1 << " - " << number2
         << " - 2 = " << subtract_result << endl
         << number1 << " * " << number2
         << " = " << multiply_result << endl
         << number1 << " / " << number2
         << " = " << divide_result << endl;
       return 0;
    }


    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:

    int var;
    var = 3 + 8;
    cout << var << endl;
    var = 100;
    cout << var << endl;


    String Theory

    Before we leave, let's write one final example, one that uses chars and strings, just for completeness.

    #include <iostream>
    #include <string>

    int main( void )
    {
       string firstName;
       char middleInitial;
       string lastName;
       string fullName;
       firstName = "George";
       middleInitial = 'C';
       lastName = "Scott";

       cout << "The person's name is: ";
       fullName = firstName + " " + middleInitial + ". " + lastName;
       cout << fullName << endl;
       return 0;
    }


    (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

    #include <iostream>

    int main( void )
    {
       float number1;
       float number2;

       cout << "Enter any number: ";
       cin >> number1;
       cout << "Enter another number: ";
       cin >> number2;

       cout << number1 << " + " << number2 << " = " << number1 + number2 << endl;
       return 0;
    }


    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

    #include <iostream>
    #include <string>

    int main( void )
    {
       string name;
       cout << "What is your name? ";
       cin >> name;
       cout << "Hello, " << name << "." << endl;
       return 0;
    }


    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.

    #include <iostream>

    int main( void )
    {
       int num_elements;
       int counter;
       float sum;

       cout << "How many numbers do you want to sum? ";
       cin >> num_elements;

       float list[num_elements];

       for( counter = 0; counter < num_elements; counter = counter + 1 )
       {
          cout << "Enter #" << counter + 1 << ": ";
          cin >> list[counter];
       }

       sum = 0;
       for( counter = 0; counter < num_elements; counter++ )
       {
          sum = sum + list[counter];
       }
       cout << endl;
       cout << "The sum of the above numbers is: " << sum << endl;
       return 0;
    }


    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:

    int i;
    i = 5;
    if( i > 1 )
    {
       i = 1;
    }


    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++:

    if(i >1)
    {
       i = 1;
    }


    This is OK, too:

    if ( i > 1 )
    {
       i = 1;
    }


    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:

    Operator Meaning Example Sample value
    < Less than if( i < j ) 1 < 5 will be true
    > Greater than if( i > j ) 1 > 5 will be false
    <= Less than or equal to if( i <= j ) 1 <= 5 will be true
    >= Greater than or equal to if( i >= j ) 1 >= 5 will be false
    == Equal to if( i == j ) 1 == 5 will be false
    != Not equal to if( i != j ) 1 != 5 will be true


    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:

    if( num >= 1 )
    {
       if( num <= 100 )
       {
          /* Do whatever */
       }
    }


    Fortunately, there's an easier way! You can combine conditions together using Boolean operators. Here are the two main Boolean operators:

    Operator Meaning Example Sample value
    && And if( a >= 1 && a <= 100 ) 5 >= 1 && 5 <= 100 will be true
    || Or if( a <= 100 || b <= 100 ) 5 <= 100 || 219 <= 100 will be true


    So, we could compress the above code like this:

    if( num >= 1 && num <= 100 )
    {
       /* Do whatever */
    }


    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:

    #include <iostream>
    #include <string>

    int main( void )
    {
       string password;
       cout << "Login: ";
       cin >> password;
       if( password == "joshua" )
       {
          cout << "Greetings, Professor Falken." << endl
          << "Shall we play a game?" << endl;
       }
       else
       {
          cout << "Access denied." << endl;
       }
       return 0;
    }


    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.

    #include <iostream>

    int main( void )
    {
       /* ----- A number guessing game.

          Ask for the seed number.
          Calculate the computer's number from the seed.
          Report on whether the computer's number is more or less than 5.
          Ask for the player's guess.
          Tell the player if s/he won or lost.
       */

       int seedNumber;
       int computerNumber;
       int guessedNumber;

       cout << "Guess a number from 1 to 10! Impress your friends!" << endl << endl;

       /* Ask for the seed number. */
          cout << "Enter any integer number: ";
          cin >> seedNumber;
       /* Calculate the computer's number from the seed. */
          computerNumber = (seedNumber + 97) / 3;
          computerNumber = computerNumber % 10 + 1;

       /* Report on whether the computer's number is more or less than 5. */
       if( computerNumber > 5 ) {
          cout << "The number I picked is more than five." << endl;
       } else {
          cout << "The number I picked is no more than five." << endl;
       }

       /* Ask for the player's guess. */
       cout << endl << "Guess the number (it's between 1 and 10): ";
       cin >> guessedNumber;

       /* Tell the player if s/he won or lost. */
       if( computerNumber == guessedNumber )
       {
          cout << "Congratulations! You were right!" << endl;
       }
       else
       {
          cout << "Sorry! I had picked the number " << computerNumber << endl;
       }
       return 0;
    }


    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.

    #include <iostream>

    int main( void )
    {
       int counter;
       counter = 1;
    while( counter <= 3 )
       {
          cout << "The counter is " << counter << endl;
          counter = counter + 1;
       }
       cout << "All done." << endl;
       return 0;
    }


    Let's take this step-by-step.

    1. We create a variable named counter, and set it equal to 1.
    2. We start the loop.
      1. The first thing we do is test the expression in the loop. counter is less than 3, so we continue into the loop.
      2. We print out a message, containing the value of counter, which will be 1.
      3. We increment counter by 1 (e.g., we take the value of counter, add 1 to it, then put the result back into counter, effectively increasing its value by 1). counter now equals 2.
      4. We hit the end of the loop, which causes us to go back up to the top.
    3. Back into the loop again
      1. First, we re-test the expression. counter is still less than 3, so we continue through the loop.
      2. We print out a message again; this time announcing that counter is 2.
      3. We increment counter again; this time, counter will equal 3.
      4. We've hit the end of the loop.
    4. Back to the top of the loop
      1. Let's re-test the expression. counter equals 3, which means it is less than or equal to 3, which means we go into the loop once again.
      2. We print out another message, saying that counter is 3.
      3. We increment counter; it now equals 4.
      4. The end of the loop.
    5. We jump back up to the top of the loop. This time, counter is greater than 3, so we jump down to after the end of the loop.
    6. We print out "All done."

    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:

    while( ( i <= 100 && name == "George" ) || food == 0.5 )


    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:

    1. An initialization section, which is used to set the starting value of the counter (e.g., cnt = 1)
    2. A condition section, which is used to test when to stop counting (e.g., cnt < 10)
    3. A movement section, which is used to change the counter (e.g., cnt = cnt + 1)

    Let's re-write the above example (while.cpp) to use a for() loop.

    #include <iostream>

    int main( void )
    {
       int counter;
       for( counter = 1; counter <= 3; counter = counter + 1 )
       {
          cout << "The counter is " << counter << endl;
       }
       cout << "All done." << endl;
       return 0;
    }


    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.

    #include <iostream>

    int main( void )
    {
       /* ----- A number guessing game.

          Ask for the seed number.
          Calculate the computer's number from the seed.
          Report on whether the computer's number is more or less than 5.
          While the user hasn't guessed the correct answer,
             Ask for the player's guess.
             Tell the player if s/he was correct.
             If the player lost, tell if guess was high or low.
        */

       int seedNumber;
       int computerNumber;
       int guessedNumber;

       cout << "Guess a number from 1 to 10! Impress your friends!" << endl << endl;

       /* Ask for the seed number. */
          cout << "Enter any integer number: ";
          cin >> seedNumber;
          /* Calculate the computer's number from the seed. */
          computerNumber = (seedNumber + 97) / 3;
          computerNumber = computerNumber % 10 + 1;

       /* Report on whether the computer's number is more or less than 5. */
       if( computerNumber > 5 ) {
          cout << "The number I picked is more than five." << endl;
       } else {
          cout << "The number I picked is no more than five." << endl;
       }

       /* While the user hasn't guessed the correct answer, */
       guessedNumber = 0;
       while( guessedNumber != computerNumber )
       {

           /* Ask for the player's guess. */
          cout << endl << "Guess the number (it's between 1 and 10): ";
          cin >> guessedNumber;

          /* Tell the player if s/he was correct. */
          if( computerNumber == guessedNumber )
          {
             cout << "Congratulations! You were right!" << endl;
          }
          else
          {
             /* If the player lost, tell if guess was high or low. */
             if( guessedNumber < computerNumber ) {
                cout << "Nope; too low." << endl;
             } else {
                cout << "Nope; too high." << endl;
             }
          } /* End if guess was correct */
       } /* end main loop */

       return 0;
    }


    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:
    • Functions can have with parameters, which are special variables that are sent to the function when the function is run.
    • Functions can return a value, usually indicating whether the operation performed by the function was successful.

    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():

    int main( void )
    {
       /* stuff happens */
       return 0;
    }


    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.

    #include <iostream>

    float square( float in )
    {
       return in * in;
    }


    int main( void )
    {
       float num;
       cout << "Enter any number: ";
       cin >> num;

       cout << num << " squared is " << square(num) << endl;
       return 0;
    }


    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.

    #include <iostream>

    void print_instructions( void )
    {
       cout << "Square Generator" << endl << endl
          << "This program displays the square of any number." << endl
          << "Enter any number at the prompt (e.g., 4.5), and" << endl
          << "the program will print its square." << endl << endl;
    }


    float square( float in )
    {
       return in * in;
    }

    int main( void )
    {
       float num;

       print_instructions();

       cout << "Enter any number: ";
       cin >> num;

       cout << num << " squared is " << square(num) << endl;
       return 0;
    }


    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.
      Print out the room description.
      Inform the player about what the player is carrying.
      Ask for a command.
      If the player wants to move in some direction,
        If that is possible,
          Move in that direction.
      If the player wants to pick up something,
        If the room has a torch in it,
          Pick up the torch.
        If the room has a key in it,
          Pick up the key.
        Otherwise,
          Report that there's nothing to pick up.
      If the player wants help,
          Print out a list of commands.
      If the player wants to quit,
          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:

    0 1 (north) 2 (east) 3 (south) 4 (west) 5 (inv)
    0 0 0 0 0 0 0
    1 0 2 0 0 0 0
    2 0 0 0 1 0 0


    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.

    int main( void )
       {
       bool boolHasTorch = false;
       bool boolHasKey nbsp;nbsp; = false;
       int iRoom = 1;
       int iQuit = 0;
       char cCommand;

       /* Loop until we want to quit. */
       while( iQuit == 0 iRoom != 8 )
          {


    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.

       /* Print out the room description. */
       cout << "--------------------" << endl;
       displayRoom( iRoom, boolHasTorch || travel_table[iRoom][5] == 1 );


    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.

       /* Inform the player about what the player is carrying. */
       cout << "You are carrying";
       if( boolHasTorch )
          cout << " a torch";
       if( boolHasKey )
          cout << " a key";
       if( !boolHasTorch && !boolHasKey )
          cout << " nothing";
       cout << "." << endl << endl;


    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.

       /* Ask for a command. */
       cout << "What do you want to do? ";
       cin >> cCommand;


    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.

       /* If the player wants to move in some direction,
          If that is possible,
             Move in that direction. */
       if( cCommand == 'n' || cCommand == 'N' )
          {
          if( canMoveFromRoom( iRoom, 1 ) )
             iRoom = travel_table[iRoom][1];
          }


    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.

       if( cCommand == 'e' || cCommand == 'E' )
          {
          if( canMoveFromRoom( iRoom, 2 ) )
             iRoom = travel_table[iRoom][2];
          }
       if( cCommand == 's' || cCommand == 'S' )
          {
          if( canMoveFromRoom( iRoom, 3 ) )
             iRoom = travel_table[iRoom][3];
          }
       if( cCommand == 'w' || cCommand == 'W' )
          {
          if( canMoveFromRoom( iRoom, 4 ) )
             iRoom = travel_table[iRoom][4];
          }


    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.

       /* If the player wants to pick up something, */
       if( cCommand == 'p' || cCommand == 'P' )
          {
          /* If the room has a torch in it, */
          if( travel_table[iRoom][5] == 1 )
             {
             /* Pick up the torch. */
             boolHasTorch = true;
             cout << "You have picked up a torch." << endl;
             travel_table[iRoom][5] = 0; /* Remove the torch from the room */
             }
          /* If the room has a key in it, */
          else if( travel_table[iRoom][5] == 2 )
             {
             /* Pick up the key. */
             boolHasKey = true;
             cout << "You have picked up a small gold key." << endl;
             travel_table[iRoom][5] = 0;
             }
          /* Otherwise, */
          else
             {
             /* Report that there's nothing to pick up. */
             cout << "There is nothing here to pick up." << endl;
             }
          }


    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().

       /* If the player wants help, */
       else if( cCommand == 'h' || cCommand == 'H' )
          {
          /* Print out a list of commands. */
          cout << "Valid commands are:" << endl
             << "n -- Move north" << endl
             << "e -- Move east" << endl
             << "s -- Move south" << endl
             << "w -- Move west" << endl
             << "p -- Pick up anything in the room" << endl
             << "q -- Quit the game" << endl;
          }
       /* If the player wants to quit, */
       if( cCommand == 'q' || cCommand == 'Q' )
          {
          /* Quit. */
          iQuit = 1;
          }
       cout << endl;
       if( iRoom == 8 )
          {
          cout << "Congratulations! You made it out of the mansion!" << endl;
          cout << "Well done!" << endl;
          }
       } /* end the main loop */
       cout << "Thanks for playing!" << endl;
       return 0;
       }


    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.

    /* Set up the travel table. */

    int travel_table[3][6] = { 0, 0, 0, 0, 0, 0,
                         0, 5, 3, 0, 2, 1,
                         0, 0, 1, 0, 0, 0,
                         0, 4, 0, 0, 1, 0,
                         0, 0, 0, 3, 0, 0,
                         0,-7, 0, 1, 6, 0,
                         0, 0, 5, 0, 0, 0,
                         0, 0, 0, 5, 0, 0 };


    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().

    void displayRoom( int room, bool hasTorch )
       {
       /* ----- Prints out a description of _room_, depending on whether the player has a torch or not.

       If the player doesn't have the torch, and the room has no torch
          Say that the player can't see anything.
       Otherwise,
          Print a description for each room.
       If there are any items in this room,
          Print a description of the item.
       */


    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:

       /* If the player doesn't have the torch, and the room has no torch, */
       if( !hasTorch travel_table[room][5] != 1 )
          /* Say that the player can't see anything. */
          cout << "It's very dark in here." << endl;


    Simple enough. Now let's see what happens if the player has a torch handy.

       /* Otherwise,
       nbsp;  nbsp;Print a description for each room. */
       else if( room == 1 )
          cout << "You are standing in what was once a grand foyer. Dust and cobwebs cover" << endl <<
                "everything, from the richly-panelled oak walls to the faded russet carpet." << endl <<
                "There are exits in all directions, except for the south door, which is now" << endl <<
                "completely impassable." << endl;


    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.

       else if( room == 2 )
          cout << "This is a small antechamber, probably used as a small study. A lopsided desk" << endl <<
                "crouches forlornly under an aged, cracked window. Nearby, a small collection" << endl <<
                "of books are slowly crumbling into dust. The only door leads east." << endl;
       else if( room == 3 )
          cout << "You stand in a small, wood-panelled hallway that runs north-south. A glance" << endl <<
                "at the doors on the east shows that they are firmly rusted shut; the only exits" << endl <<
                "are to the east (back to the foyer), or north." << endl;
       else if( room == 4 )
          cout << "This bathroom is in a worse state than the rest of the house. The bath tub" << endl <<
                "is covered in dust, the sink is cracked, the wallpaper has long since peeled" << endl <<
                "away, and most of the tiles are split." << endl;
       else if( room == 5 )
          cout << "This must have been a comfortable study once. All of the furniture has been" << endl <<
                "removed, though, leaving only bare bookcases and a hard wooden floor. Large" << endl <<
                "doors lead to the east and south, while a curious metal door has been set into" << endl <<
                "the wall to the north." << endl;
       else if( room == 6 )
          cout << "This cramped, dusty pantry contains nothing of interest." << endl;
       else if( room == 7 )
          cout << "You are in a tiny elevator that looks like it was made many decades ago. But" << endl <<
                "more importantly, the cage has been broken open to the north. You can see" << endl <<
                "daylight pouring in from that direction!" << endl;


    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.

       /* If there are any items in this room,
          Print a description of the item. */
       if( travel_table[room][5] == 1 )
          cout << "There is a flickering torch here." << endl;
       else if( travel_table[room][5] == 2 )
          cout << "You can see a small key lying on the ground here." << endl;
       } /* end function */


    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:

    bool canMoveFromRoom( int room, int direction )
       {
       /* If the way is 0, */
       if( travel_table[room][direction] == 0 )
          {
          /* Say that you can't go there, and return false. */
          cout << "You can't go that way." << endl;
          return false;
          }
       /* If thet way is negative, */
       else if( travel_table[room][direction] < 0 )
          {
          /* Say that that way is locked, and return false. */
          cout << "That door is locked." << endl;
          return false;
          }
       /* Otherwise, */
       else
          {
          /* Return true. */
          return true;
          }
       }


    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:

    1. Add a health counter, which goes down every time the player enters a command. If the healther counter gets to 0, quit the game.
    2. Add food packets that, when picked up, are immediately consumed and increases the player's health counter.
    3. Create another locked door in the game, this one requiring a silver key.
    4. Add more rooms to the game, updating the travel table and room descriptionsas appropriate.

    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:

    #include <Window.h>
    #include <Button.h>
    #include <TextView.h>


    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:

    class MyButton : public BButton
    {
       MyButton( void );
       void SetValue(int32 value);
    }


    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.

    class HelloWorldApp : public BApplication
    {
       public:
          HelloWorldApp(); /* constructor */
       private:
          MainWin *myMainWindow;
    };


    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.

    HelloWorldApp :: HelloWorldApp()
              : BApplication( "application/x-vnd.Tutorial-HelloWorld" )
    {
       BRect rectWindowSize;
       rectWindowSize.Set( 50, 50, 150, 150 );
       myMainWindow = new MainWin( rectWindowSize );
    }


    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.

    class MainWin : public BWindow
    {
       public:
          MainWin( BRect winSize ); /* constructor */
          virtual bool QuitRequested();
    };


    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.

    MainWin :: MainWin( BRect winSize )
          : BWindow( winSize, "Hello World", B_TITLED_WINDOW, B_NOT_RESIZABLE | B_NOT_ZOOMABLE )
    {
       /* Set up MainView as a view, filling up the entire screen (thus Bounds()) */
       AddChild( new MainView( Bounds() ) );
       Show();
    }


    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.

    bool MainWin :: QuitRequested()
    {
       be_app<7FONT>->PostMessage( B_QUIT_REQUESTED );
       return true;
    }


    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).

    class MainView : public BView
    {
       public:
          MainView( BRect windowSize ); /* constructor */
          virtual void Draw( BRect rectUpdate );
    };


    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.

    MainView :: MainView( BRect windowSize )
          : BView( windowSize, "HelloView", B_FOLLOW_ALL_SIDES, B_WILL_DRAW )
    {
       /* Just let the BView construtor do its thing */
    }


    Boy, this is boring.

    void MainView :: Draw( BRect rectUpdate )
    {
       MovePenTo( BPoint( 20, 50 ) );
       DrawString( "Hello, World!" ); /* Phew! Finally draw our message! */
    }


    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.

    void main( void )
    {
       HelloWorldApp myApp;
       myApp.Run();
    }


    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.

    #include <Application.h>
    #include <Window.h>
    #include <View.h>


    All right! We've got a complete BeOS Hello World program coded! Here's how the code should look:

    #include <Application.h>
    #include <Window.h>
    #include <View.h>


    class MainView : public BView
    {
       public:
          MainView( BRect windowSize ); /* constructor */
          virtual void Draw( BRect rectUpdate );
    };

    MainView :: MainView( BRect windowSize )
          : BView( windowSize, "HelloView", B_FOLLOW_ALL_SIDES, B_WILL_DRAW )
    {
       /* Just let the BView construtor do its thing */
    }

    void MainView :: Draw( BRect rectUpdate )
    {
       MovePenTo( BPoint( 20, 50 ) );
       DrawString( "Hello, World!" ); /* Phew! Finally draw our message! */
    }

    class MainWin : public BWindow
    {
       public:
          MainWin( BRect winSize ); /* constructor */
          virtual bool QuitRequested();
    };

    MainWin :: MainWin( BRect winSize )
          : BWindow( winSize, "Hello World", B_TITLED_WINDOW, B_NOT_RESIZABLE | B_NOT_ZOOMABLE )
    {
       /* Set up MainView as a view, filling up the entire screen (thus Bounds()) */
       AddChild( new MainView( Bounds() ) );
       Show();
    }

    bool MainWin :: QuitRequested()
    {
       be_app->PostMessage( B_QUIT_REQUESTED );
       return true;
    }

    class HelloWorldApp : public BApplication
    {
       public:
          HelloWorldApp(); /* constructor */
       private:
          MainWin *myMainWindow;
    };

    HelloWorldApp :: HelloWorldApp()
          : BApplication( "application/x-vnd.Tutorial-HelloWorld" )
    {
       BRect rectWindowSize;
       rectWindowSize.Set( 50, 50, 150, 150 );
    myMainWindow = new MainWin( rectWindowSize );
    }

    void main( void )
    {
       HelloWorldApp myApp;
       myApp.Run();
    }


    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

    • Change the size of the displayed window.
    • Change the message displayed, from "Hello, World!" to "Thank you, come again." Make sure it fits.
    • Find out how to change the title of the project from "BeApp" to "HelloWorld" by looking around in CodeWarrior's preferences.

    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.

    class OrderCalcView : public BView
    {
       public:
          OrderCalcView( BRect frame ); /* constructor */
          void MessageReceived( BMessage *message );
          BTextControl *tctrlKeyboard;
          BTextControl *tctrlErgKeyboard;
          BTextControl *tctrlMouse;
          BTextControl *tctrlTrackball;
          BStringView *sviewSalesTax;
          BButton *btnProcess;
    };


    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.

    OrderCalcView :: OrderCalcView( BRect frame )
          : BView( frame, "OrderCalcView", B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW | B_NAVIGABLE )
    {
       BRect r;
       BMessage *msg;
       /* Set up Keyboard */
       r.Set( 5, 10, 305, 30 );
       tctrlKeyboard = new BTextControl( r, "Keyboard", "Keyboard ($30):", "", NULL );
       AddChild( tctrlKeyboard );
       /* Set up Ergonomic Keyboard */
       r.Set( 5, 45, 305, 65 );
       tctrlErgKeyboard = new BTextControl( r, "ErgKbd", "Ergonomic Keyboard ($90):", "", NULL );
       AddChild( tctrlErgKeyboard );
       /* Set up Mouse */
       r.Set( 5, 70, 305, 90 );
       tctrlMouse = new BTextControl( r, "Mouse", "Mouse ($25):", "" NULL );
       AddChild( tctrlMouse );
       /* Set up Trackball */
       r.Set( 5, 95, 305, 115 );
       tctrlTrackball = new BTextControl( r, "Trackball", "Trackball ($45):", "", NULL );
       AddChild( tctrlTrackball );
       /* Set up Sales Tax */
       r.Set( 5, 120, 305, 140 );
       sviewSalesTax = new BStringView( r, "Tax", "Sales Tax is 0.45%" );
       AddChild( sviewSalesTax );
       /* Set up Process Button */
       r.Set( 105, 150, 205, 175 );
       msg = new BMessage( 1 );
       btnProcess = new BButton( r, "Process", "Process", msg );
       AddChild( btnProcess );
    }


    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().

    void OrderCalcWindow :: MessageReceived( BMessage *message )
    {
       int numKeyboards = 0, numErgKeyboards = 0, numMice = 0, numTrackballs = 0;
       int subtotal = 0;
       char temp[512];
       BAlert *alert;
       switch( message->what )
       {
          case 1:
             numKeyboards = atoi( view->tctrlKeyboard->Text() );
             numErgKeyboards = atoi( view->tctrlErgKeyboard->Text() );
             numMice = atoi( view->tctrlMouse->Text() );
             numTrackballs = atoi( view->tctrlTrackball->Text() );
             subtotal = (numKeyboards * 35)
                + (numErgKeyboards * 90)
                + (numMice * 25)
                + (numTrackballs * 45)
             sprintf( temp, "Total: %f", (float)(subtotal + (subtotal * 0.0045)) );
             alert = new BAlert( "total", temp, "OK", "", "" );
             alert->Go();
             break;
          default:
             BWindow :: MessageReceived( message );
             break;
       }
    }


    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

    • Clear out the text boxes after displaying the total.
    • Make each text box display "0" after displaying the total.
    • Use text boxes for the prices as well as the amounts.

    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

    #include <Application.h>
    #include <Window.h>
    #include <View.h>
    #include <TextControl.h>
    #include <StringView.h>
    #include <Button.h>

    const unsigned int CHECK_EMAIL = 1;


    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.

    class ViewEMail : public BView
    {
       public:
          ViewEMail( BRect frame );
          bool checkEMail( void );


    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.

       private:
          BTextControl *tctrlServer, *tctrlUsername, *tctrlPassword;
          BStringView *sviewResult;
          BButton *btnCheckEMail;
    }


    We have three sorts of controls here. Let's look at each type.

    • A BTextControl displays a label and a text box. We've created three of them, one so that the user can enter in the name of his or her POP e-mail server, one for the user's username on that server, and one for the user's password on that server.
    • A BStringView simply displays a string of text. This is what will display the result of connecting to the server.
    • A BButton displays an actual push-button, which the user will press when s/he wants to connect.

    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.

    ViewEMail :: ViewEMail( BRect frame )
          : BView( frame, "ViewEMail", B_FOLLOW_ALL_SIDES, B_WILL_DRAW | B_NAVIGABLE )
    {
       BRect r;
       /* Server */
       r.Set( 5, 5, 200, 50 );
       tctrlServer = new BTextControl( r, "Server", "Server:", "", NULL );
       AddChild( tctrlServer );


    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.

       /* Username */
       r.Set( 5, 55, 200, 100 );
       tctrlUsername = new BTextControl( r, "Username", "User name:", "", NULL );
       AddChild( tctrlUsername );
       /* Password */
       r.Set( 5, 105, 200, 150 );
       tctrlPassword = new BTextControl( r, "Password", "Password:", "", NULL );
       AddChild( tctrlUsername );
       /* "Check E-Mail" button */
       r.Set( 55, 155, 150, 200 );
       btnCheckEMail = new BButton( r, "CheckEMail", "Check", new BMessage( CHECK_EMAIL ) );
       AddChild( btnCheckEMail );


    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.

       /* Result string view, to display result */
       r.Set( 5, 205, 200, 250 );
       sviewResult = new BStringView( r, "sviewResult", "", NULL );
       AddChild( sviewResult );
    }


    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

    bool ViewEMail :: checkEMail( void )
    {
       /* Connect to the server.
       Send username.
       Send password.
       Ask for status.
       Display status.
       */


    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.

       BNetEndpoint *connection;


    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.

       char buffer[1025];
       string strMessage;
       string strNumEMails;
       const string strCRLF = "" + (char)(13) + (char)(10); status_t result;
       if( strcmp( tctrlServer->Text(), "" ) == 0
          || strcmp( tctrlUsername->Text(), "" ) == 0
          || strcmp( tctrlPassword->Text(), "" ) == 0 )
          return false; /* User must fill out fields */


    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.

       /* Connect to the server. */
       connection = new BNetEndpoint();
       result = connection->Connect( tctrlServer->Text(), 110 );
       if( result != B_OK )
          return false; /* Couldn't connect */


    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.

       connection->Receive( buffer, 1024 );
       if( buffer[0] != '+' && buffer[1] != 'O' && buffer[2] != 'K' )
          return false; /* Connection refused */


    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.

       /* Send username. */
       strMessage = "USER " + (string)tctrlUsername->Text() + strCRLF;
       connection->Send( strMessage.c_str(), strMessage.length() );
       connection->Receive( buffer, 1024 );
       if( buffer[0] != '+' && buffer[1] != 'O' && buffer[2] != 'K' )
          return false; /* Bad 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.

       /* Send password. */
       strMessage = "PASS " + (string)tctrlPassword->Text() + strCRLF;
       connection->Send( strMessage.c_str(), strMessage.length() );
       connection->Receive( buffer, 1024 );
       if( buffer[0] != '+' && buffer[1] != 'O' && buffer[2] != 'K' )
          return false; /* Bad 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.

       /* Ask for status. */
       strMessage = "STAT" + strCRLF;
       connection->Send( strMessage, 6 );
       connection->Receive( buffer, 1024 );
       strMessage = (string)buffer;


    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.

       if( strMessage.substr( 0, 3 ) != "+OK" )
          return false; /* STAT didn't work */


    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.

       /* Close connection */
       strMessage = "QUIT" + strCRLF;
       connection->Send( strMessage, 6 );
       connection->Close();


    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.

       /* Set up the status, based on where the number ends */
       if( strMessage[5] == ' ' )
          strNumEMails = strMessage.substr( 4, 1 );
       else if( strMessage[6] == ' ' )
          strNumEMails = strMessage.substr( 4, 2 );
       else if( strMessage[7] == ' ' )
          strNumEMails = strMessage.substr( 4, 3 );
       else
          strNumEMails = strMessage.substr( 4, 4 );


    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.

       /* Display status. */
       strMessage = "You have " + strNumEMails + " e-mail(s)."
       sviewResult->SetText( strMessage.c_str() );
       return true;
    }


    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.

    WinEMail :: WinEMail( BRect frame )
          : public BWindow( frame, "E-Mail Checker", B_TITLED_WINDOW, B_ASYNCHRONOUS_CONTROLS )
    {
       view = new ViewEMail( Bounds() );
       AddChild( view );
       MakeFocus( view );
       Show();
    }


    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.

    void WinEMail :: MessageReceived( BMessage *message )
    {
       switch( message->what )
       {
          case CHECK_EMAIL:
          view->checkEMail();
          break;


    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.

          default:
             BWindow :: MessageReceived( message );
             break;
       }
    ;}


    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

    class RaycasterView : public BView
    {
       public:
          RaycasterView(BRect frame);
          virtual void KeyDown( const char *bytes, int32 numBytes );
          virtual void Draw(BRect updateRect);
       private:
          void DrawWorld( void ); /* Draws the forward view */
          float player_x, player_y, player_angle; /* Player position */
          int wall_grid[12][12]; /* The level */
          float SineFunctions[360]; /* Cached sine and cosine values */
          float CosineFunctions[360];
    };


    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.

    RaycasterView :: RaycasterView(BRect frame)
          : BView( frame, "MainView", B_FOLLOW_ALL_SIDES, B_WILL_DRAW )
    {
       int i;
       /* Brent's boring (yawn) table creation.
          This caches the sine and cosine functions so we don't have to
          actually calculate sin() and cos() constantly in the program. */
       for( i = 0; i <= 360; i++ )
          SineFunctions[i] = sin((float)i * 0.0174) * 100;
       for( i = 0; i <= 360; i++ )
          CosineFunctions[i] = cos((float)i * 0.0174) * 100;


    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.

    /* Set up level data */
    for( i = 0; i <= 12; i++ ) /* The walls bordering the world */
       {
          wall_grid[i][0] = 1;
          wall_grid[i][12] = 1;
          wall_grid[0][i] = 1;
          wall_grid[12][i] = 1;
       }
       for( i = 4; i <= 12; i++ ) /* Inner walls */
          wall_grid[i][4] = 1;
       for( i = 1; i <= 10; i++ )
          wall_grid[i][7] = 1;
       /* Set up initial player position */
       player_x = 15;
       player_y = 35;
       player_angle = 0;
       /* Display the world */
       DrawWorld();
    }


    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

    void RaycasterView :: KeyDown( const char *bytes, int32 numBytes )
    {
       if( bytes[0] == B_RIGHT_ARROW )
       {
          /* Rotate the player three degrees to the right and redraw */
          player_angle = player_angle + 3;
          DrawWorld();
       }
       else if( bytes[0] == B_LEFT_ARROW )
       {
           /* Rotate the player three degrees to the left and redraw */
          player_angle = (int)(player_angle + 357) % 360;
          DrawWorld();
       }


    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.

       else if( bytes[0] == B_UP_ARROW )
       {
          /* Move the player forward and redraw, if possible */
          float old_player_x, old_player_y;
          /* Save current position */
          old_player_x = player_x;
          old_player_y = player_y;
          /* Calculate the player's new position */
          player_x = player_x + (SineFunctions[(int)(player_angle + 39) % 360] / 50);
          player_y = player_y + (CosineFunctions[(int)(player_angle + 39) % 360] / 50);
          /* Walking through a wall? */
          if( wall_grid[(int)(player_y / 10)][(int)(player_x / 10)] > 0 )
          {
              /* If so, don't update position */
             player_x = old_player_x;
             player_y = old_player_y;
          }
          else {
          DrawWorld();
          }
       }


    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.

       else if( bytes[0] == B_DOWN_ARROW )
       {
          /* Move the player backward and redraw, if possible */
          float old_player_x, old_player_y;
          /* Save current position */
          old_player_x = player_x;
          old_player_y = player_y;
          /* Calculate the player's new position */
          player_x = player_x - (SineFunctions[(int)(player_angle + 39) % 360] / 50);
          player_y = player_y - (CosineFunctions[(int)(player_angle + 39) % 360] / 50);
          /* Walking through walls? */
          if( wall_grid[(int)(player_y / 10)][(int)(player_x / 10)] > 0 )
          {
              /* If so, don't update position */
             player_x = old_player_x;
             player_y = old_player_y;
          }
          else {
             DrawWorld();
          }
       }


    And now the end of the KeyDown() code, which deals with [T] and [ESC].

       else if( bytes[0] == 't' || bytes[0] == 'T' )
       {
           /* Teleport back to original position. Silly, but eh. */
          player_x = 15;
          player_y = 35;
          player_angle = 0;
          DrawWorld();
       }
       else if( bytes[0] == B_ESCAPE )
       {
           /* Quit */
          exit( 0 );
       }
    }


    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.

    void RaycasterView :: Draw(BRect updateRect)
    {
       /* Called when the view is first being displayed, or being redrawn. */
       BPoint start, end;
       /* Draw the main bit */
       DrawWorld();
       /* Now draw all the extra stuff */
       start.Set( 395, 0 );
       end.Set( 395, 200 ); /* Draw line on right */
       StrokeLine( start, end, B_SOLID_HIGH );
       /* Display silly text messages */
       start.Set( 415, 85 );
       DrawString( "Ray", start, NULL );
       start.Set( 407, 100 );
       DrawString( "caster", start, NULL );
       start.Set( 418, 155 );
       DrawString( "by", start, NULL );
       start.Set( 409, 170 );
       DrawString( "Brent", start, NULL );
       start.Set( 402, 185 );
       DrawString( "Newhall", start, NULL );
    }


    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.

    void RaycasterView :: DrawWorld( void )
    {
       float ray_x, ray_y;
       int ray_angle, ray_length;
       float x_increment, y_increment, wall_height;
       int wall, X;
       BRect rect;
       /* Erase whatever was on the screen */
       rect.Set( 0, 0, 394, 200 );
       FillRect( rect, B_SOLID_LOW );


    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.

       /* Loop from player_angle, over an 80-degree swath */
       for( ray_angle = (int)player_angle; ray_angle < (int)player_angle + 79; ray_angle++ )
       {
          /* Calculate movement based on current angle */
          x_increment = SineFunctions[ray_angle % 360] / 100;
          y_increment = CosineFunctions[ray_angle % 360] / 100;
          /* Ray starts at player's position, with a length of 0 */
          ray_x = player_x;
          ray_y = player_y;
          ray_length = 0;
          wall = 0; ;


    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.

          /* Loop until the ray hits something */
          while( wall == 0 )
          {
              /* Move the ray out */
             ray_x = ray_x + x_increment;
             ray_y = ray_y + y_increment;
             ray_length = ray_length + 1;
             /* Find out what it's hit, if anything, on the grid */
             wall = wall_grid[(int)(ray_y / 10)][(int)(ray_x / 10)];
          } /* end while no 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.

          /* Get a practical height for the wall segment */
          wall_height = 1000 / ray_length;
          /* Find the correct position on the screen */
          X = (ray_angle - (int)player_angle) * 5;
          rect.Set( X, 100 - wall_height, X + 4, 100 + wall_height ); /* Draw wall segment */
          FillRect( rect, B_SOLID_HIGH );
       } /* end for(ray_angle) */
    }


    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



    Tutorial by Brent P. Newhall / November 2000
    Made available by BeSly, the BeOS, Haiku and Zeta knowledge base.