Channels ▼


Windows: Casting Rays and Developer Days

Source Code Accompanies This Article. Download It Now.


I recently attended one of 15 simultaneous, around-the-country sessions of Microsoft's Developer Days, small regional conferences hosted by local Microsoft-devoted software-development companies and attended by area programmers. I went to the Fort Lauderdale session, about three hours from home. Attendees paid $50 in advance or $75 at the door, for which they got one day chock-full of presentations about Microsoft development tools. As a bonus, there were a few vendor booths around the perimeter of the room, but nothing impressive to a veteran Software Development '95 or Borland conference attendee.

The program started with a general session wherein the local hosts introduced themselves and some token Microsoft participants. We burned some time that way while waiting for the West Coast to get out of bed. Then we spent the rest of the morning watching announcements and presentations of Microsoft products via live satellite video from Redmond. This pitch took until lunch time and was little more than a high-hype Microsoft infomercial. It even had the glossy look of those late-night promotional TV programs that sell real-estate-investment courses, exercise equipment, diet programs, and so on. The tone was set by an oh-so-sincere Microsoft veep, who waved his arms and proclaimed in a high-pitched, shrill voice how wonderful our lives will be as soon as we buy all these new Microsoft offerings. As he went on and on, saying nothing of substance, you could close your eyes and substitute the face of that obnoxious infomercial kid pitching his fast-money-making course, promising you "...four ways to make money: running ads, buying and selling, getting a 1-900 number..." and I forgot the fourth one. The veep and that kid must have the same public-speaking coach. This sure wasn't worth fifty bucks, not to mention getting out of bed at 5 am and driving three hours. (Just to be fair, I should tell you that my press credentials, not my fifty bucks, got me in the door.)

The infomercial continued with four carefully rehearsed product announcements. The scripts emulated those Tony Roberts/Fran Tarkenton smart-guy/dumb-guy roles. The smart guy would demonstrate some cool feature and the dumb guy would ask a programmed question designed to elicit an appropriate positive response from the smart guy. Remember, this was a live telecast, and in the Visual Basic 4.0 segment, the smart guy was demonstrating OLE servers across a network. The dumb guy either improvised or couldn't see the Teleprompter and asked a really dumb question. Something like, "Wow, sending objects across a network is really very revolutionary, isn't it?" The audience laughed. The smart guy stifled a giggle and then shot the dumb guy a look which seemed to say, "You really are a dumb guy, aren't you?" I didn't know Visual Basic supported type casting.

After lunch, which was both provided and excellent, we broke into separate sessions, based on our interests. This is where the trip paid for itself. If you attend one of these sessions in the future, skip the general session and show up in time for lunch. I doubt that you'll miss anything important.

Of the four announcements, the one that interested me was Visual C++ 4.0. There were two VC++ sessions conducted by area developers with the assistance of a member of Microsoft's VC++ development team. The first addressed the new features of Visual C++ 4.0, and the second was about MFC 4.0. Microsoft skipped version 3.x for VC++ to align the version numbers between the compiler and the class library. Henceforth, they'll be released together.

I've been using a beta of VC++ 4.0 for a while now, have become a fan, and was looking forward to seeing it in the hands of experts. One prominent feature is the ability to define custom App Wizards. You can build application templates and distribute them as App Wizards with which other programmers can build their applications. Expect to see some third-party application-specific frameworks distributed this way. I'm investigating whether the Windows game engine we're developing for a new book will fit into that model.

Microsoft is marketing a source-code version-control package called "Source Safe," and, of course, it integrated the package into the visual components of its language tools. It uses a Windows 95 Explorer interface and seems to be intuitive. When you view your list of source files in Visual C++, a check mark indicates whether you have the file checked out for modification or if it is still sacrosanct in the baseline copy of the developing project. Several developers complained that they still cannot do anything to manage a resource file when several programmers are working on the same project. Resource (.RC) files contain resource-language source code that defines menus, bitmaps, dialog boxes, strings, and so on. There is only one ASCII resource file for a program; no way exists to automatically manage its configuration when several programmers and screen designers are simultaneously messing with its contents. Someone asked how Microsoft deals with the problem, given that they build enormous projects with many programmers. The answer reminded me of Carol Burnette's response to the question, "How do you dance here at Camp Sunny nudist colony?" "Very carefully," she said.

The VC++ presenter knew her stuff and showed the tool to advantage. She was best with her prepared pitch, but some questions stumped her, and the Microsoft guy jumped in and filled in the blanks. They both seemed to understand the new Developer Studio and MFC really well, but they weren't particularly current on C++ language developments. When asked if VC++ 4.0 supports the ANSI mutable keyword and bool type specifier, their blank stares revealed that neither of them even knew about these language features. A search in the help file turned up no such references, so we assumed for the moment that VC++ 4.0 does not support them. The Microsoft guy tried to slough it off by saying something like, Well there is the accepted standard, and then there are the proposed features. Horse hockey. There is the standard as published by ANSI for public review. Period. It represents what the committee has approved and codified. I assume that Microsoft has a copy. They ought to let their compiler developers read it. Both mutable and bool (and true and false) are prominent in the keyword list of that document--on page 2-4, for those of you who do have a copy.

Borland C++ has supported a subset of the new ANSI features for some time now, but it does not include mutable or bool. Borland's chief compiler architect bolted to Microsoft a while back. Now Microsoft C++, which was always way behind in the new feature department, has close to that same subset. Probably just a coincidence.

I tried both keywords on my beta of VC++ 4.0, and they are indeed not there. There's still time, however. The product release is not due for another six weeks.

The session devoted to MFC was a delight, due largely to the knowledge and humor of the presenter, Mike LaRue of Decision Consultants (Clearwater, FL). His whimsical "feature list" guided him through some revealing demonstrations in which he built programs and OLE controls from scratch on the podium with everyone watching and listening. I gained a lot of insight into the improvements that MFC 4.0 brings to Windows programming, particularly in the areas of OLE controls, OLE automation, and something called "message reflection." In his demonstration, Mike designed a button control that provided its own behavior. Mike stored the new control in the Component Gallery. Then he showed how he could include the new button in any other project without having to port the behavior-defining button-click code. Neat stuff.

The Raycaster Project

Last month I introduced the Raycaster project, a DOS raycasting game engine in C++. This month sees some additions and changes to the project and a port to Windows. The DOS version is virtually complete. It contains almost everything I need to develop the simulation that the program was designed to support. The Windows version works, but not as well as I'd like. More about that later. First, I'll discuss the changes to the engine.

GFX Files

In its first version, the raycasting engine used .PCX files for the texture maps of wall tiles and props. This month, I add animated sprites to the engine. It occurred to me that distributing games--particularly shareware games--in file formats that users can change could be dangerous. Someone could add graffiti to a wall tile or features to a sprite and re-upload your game with their modifications. Unsuspecting downloaders might think that they were getting the original.

Consequently, I created a generic bitmap-library format that stores one palette and some number of bitmaps. The game engine now uses that format rather than .PCX files. I wrote a utility program to create the libraries from listed collections of .PCX files. Listing One is gfxmake.cpp and Listing Two is cmdline.cpp. These files, along with the pcx and bitmap classes from the engine, constitute the program that builds the library. The cmdline.cpp file implements a generic command-line parser to read filenames from either the command line or a response file and execute a callback function for each one.

Compressing the Maze

The game's maze was previously defined in an ASCII text file described last month. To hide its contents from interlopers, I wrote a program that compresses the file with a simple run-length encoding algorithm. The engine now loads the maze data file by using a matching decompression algorithm. Listing Three is bldmaze.cpp, the program that builds the compressed maze-data file.


The first incarnation of the Raycaster project did not support animated sprites, although it did support immovable props. The prop logic was flawed, and putting props in certain locations in the maze revealed bugs in the way their positions were computed and their hidden slices were suppressed. When I added sprites to the engine, I overhauled the prop logic as well, and those bugs went away.

A sprite is represented by a set of 24 32x64 bitmaps. There are three sets of eight frames each. Each set represents the three frames of an animated walk. One set has the left foot forward, the second has the right leg forward, and the third has the two legs together. The third set also represents the sprite standing still. The eight frames in each set represent the sprite as viewed from the front, back, either side, and any of the four quartering views. From these bitmaps a sprite is rendered based on its current mode with respect to walking or standing, the direction the sprite is facing, and the direction from which the game player is viewing the sprite.

The game program instantiates a sprite object, and provides its position and orientation in the maze, and its current stepping mode. As the game progresses, the game program changes those values to reflect sprite movements and turns. The raycaster renders a sprite if it is in view depending on all the factors that would select an appropriate frame.

Eventually, sprites will need to do more than just walk and stand. In the traditional 3-D maze game, the sprites fire at the player and fall down when fired upon. I have not worked those sequences out yet, but their implementations should be a minor extension of the walking sequence. The hard work is rendering the frames from several views.

Floors and Ceilings

Last month I indicated that I did not want texture-mapped floors and ceilings in the maze game. Since then, I've changed my mind. In Gardens of Imagination (The Waite Group, 1994), Christopher Lampton implements floor and ceiling texture mapping in a demo of his raycaster. Using his example as a guide, I added floors and tiles to my raycaster, only to find two insidious problems: As I moved around the maze, the floors and ceilings shifted under the walls; and as I changed viewport sizes, the floors and ceilings did not change appropriately. Thinking that I probably misinterpreted the code, I returned to Lampton's demo and discovered that his program has the shifting problem, too. He does not provide for changing viewport sizes, so I don't know about that part of his algorithm. By cobbling and tweaking away at the math, I was able to make everything work properly.

Texture-mapped floors and tiles are a mixed blessing. The screens are lovely to look at. Tiled ceilings and floors add an impressive dimension of reality to any view from within the maze. But the processing cycles to render them are extensive. In a big room with an expanse of floor and ceiling to render, the frame refresh rate goes way down. I determined that texture-mapped floors and ceilings should be used only in small rooms in the maze to minimize their overhead.

The increased overhead stems from the way the program renders walls and ceilings with perspective. A raycaster makes one horizontal pass across the viewport for each frame of walls. The number of passes depends on the number of horizontal pixels in the viewport. All the calculations for a slice are based on the distance of the wall slice from the viewer, and no vertical casting is necessary. Casting slices for walls and floors, however, involves an addition trace up the floor from the bottom of the viewport to the bottom of the nearest wall in the current slice. For each vertical point in the slice, the algorithm chooses the correct pixel from the texture map, and that's where the overhead comes in. For slow machines, it is better to just leave out the textured walls and ceilings, using solid colors instead. Or, you could take a whack at optimizing the algorithm with assembly language.

Tubas of Terror Demo

Listing Four is main.cpp, the program that uses the RayCaster and Sprite classes to implement a demo of a DOS game called Tubas of Terror. The game demo is incomplete. You can wander around in the maze, and the sprite, a tuba player, paces back and forth. Other than that, nothing happens, but the primitives are in place for a more-complete game, which I plan to finish soon.

To implement the demo, I derived a TubasOfTerror class from the RayCaster class and a TubaPlayer class from the Sprite class. The TubasOfTerror class object instantiates an object of the TubaPlayer class, which contains member functions that step the sprite forward and do an about-face. The main function instantiates a TubasOfTerror object and uses it to display the maze rooms. A keyboard object provides game controls to move the game player around in the maze. The game program walks the sprite up and down in its path. Presumably, the player would be able to blow a blast at the sprite to knock it down, and the sprite would be able to do likewise. A real game would have the sprite(s) wander around in the maze looking or waiting for the player.

Raycasting in Windows

I developed all this code with Borland C++ 4.5 to run under DOS. If you download the program and use the makefile, it should build the DOS utility programs, the game data files, and the DOS demo game program. A port to Windows of the raycasting engine and demo is included. I developed that port with a beta of Visual C++ 4.0, and the download includes the attendant source code for that port, along with the project makefile. The entire zipped project file unzips to the directory structure that you need for both versions.

The main.cpp file is replaced by source-code files that implement MFC CWinAPP and CFrameWnd derivative classes. The DOS vga.h and vga.cpp files are replaced by source-code files that implement the VGA class to operate in the Windows Win32 Dib environment. Files that implement the keyboard and an ASSERT macro are eliminated.

I learned a lot during this Windows port. First, the program, which uses Win32 Dib logic to write to the frame window, is not as fast, by a small margin, as its DOS counterpart, which writes directly to video memory. Second, a 320x200 screen in DOS uses the full screen. The same image in a Windows Dib uses 320x200 pixels in whatever resolution Windows is using. In standard 640x480 mode, the raycaster casts an image that occupies half the screen area. In larger resolutions, the image is proportionately smaller. The DOS raycaster runs a lot better in a lower resolution. 240x160 is more efficient and quite comfortable on a DOS screen, assuming that some of the screen's 320x200 real estate would be used for scoreboards and so on. That size on an 800x600 Windows display presents barely discernible walls, props, and sprites. You can try it out for yourself. Both versions of the demo use the plus and minus keys to change the viewport size. Watch the performance change as the viewport gets bigger and smaller. Observe the difference as you enter rooms with texture-mapped floors and ceilings. The DOS demo does not trigger frames on a timer tick. Therefore, when you go into the large room where the sprite is pacing, it seems to be running (depending on the speed of your processor). But when you follow the sprite into the room with a tiled floor and ceiling, the sprite slows down considerably. That difference illustrates the overhead needed to cast those floor and ceiling slices.

The Windows performance problem is an indication of why there have not been any great animation games developed for Windows. We've all heard about WinDoom--has anyone seen it? The problems, real as they are, are addressed by something new called the Windows 95 Game SDK, still in beta. I have a copy of the beta, which arrived by surprise in the mail one day. I have subsequently tried unsuccessfully for four months to become a certified beta tester so that I could tap into the secret meetings on CompuServe, get advice, and download drivers. No luck. I even signed and mailed in an unsolicited NDA, which was apparently ignored. Therefore I feel no qualms about discussing the product, which the rumor mill tells me is soon to be released anyway. Wonder if they'll let me buy one.

The Game SDK consists of DirectDraw, DirectSound, DirectInput, and DirectPlay, which handle real-time demands for video, sound, input devices, and network access, respectively, under Windows 95. I've looked only at the video part and should soon have the raycaster ported to use it. Applications programmed to use the Game SDK assume the presence of accelerated drivers and high-performance hardware. They work with my 32-bit ATI Mach32, but not very well. The same programs scream on a machine with a 64-bit card. As usual, software development advances the mainstream of hardware. I'll report more on my progress with the Games SDK when I've made enough progress to report.

Source Code

The source-code files for the Raycaster project are free. You can download them from the DDJ Forum on CompuServe, the Internet by anonymous ftp, and elsewhere; see "Availability," page 3.

If you cannot get to one of the online sources, send a 3.5-inch diskette and an addressed, stamped mailer to me at Dr. Dobb's Journal, 411 Borel Avenue, San Mateo, CA 94402, and I'll send you the source code. Make sure that you include a note that says which project you want. The code is free, but if you care to support my Careware charity, include a dollar for the Brevard County Food Bank.

Listing One

#include <fstream.h>
#include <dir.h>
#include "pcx.h"
#include "utils.h"
static void create_gfxlib(int argc, char *argv[]);
static PCXBitmap* bitmaps[bitmapcount];
static SHORT count;
int main(int argc, char *argv[])
    if (argc<3)     {
        cout <<
        "\nUSAGE : GFXMAKE <gfxlib> <file1, ...> @<listfile>\n"
        "  Builds graphics library file <gfxfile>\n"
        "  from the files listed on the command line or in\n"
        "  <listfile>. GFXMAKE constructs the GFX library\n"
        "  with the entries in the order in which they appear\n"
        "  on the command file and/or in <listfile>.\n";
        return -1;
    create_gfxlib(argc, argv);
    return 0;
static void bld_gfxlib(const char *file);
static void create_gfxlib(int argc, char *argv[])
    ofstream ofile(argv[1],ios::binary);
    if ( {
        cout << "Cannot open " << argv[1] << endl;
    count = 0;
    parse_cmdline(argc-2, argv+2, bld_gfxlib);
    // ----- write the signature
    ofile.write("GFX", 3);
    // --- record the common palette
    ofile.write(bitmaps[0]->GetPalette(), palettelength);
    // --- record number of bitmaps in GFX
    // --- record the bitmaps
    for (SHORT i = 0; i < count; i++)    {
        Bitmap& bm = *bitmaps[i];
        WORD height = bm.Height();
        WORD width  = bm.Width();
        const char* buf  = bm.GetPixelmap();
        const char* name = bm.Name();
        ofile.write(name, namelength);
        ofile.write((char*)&height, sizeof(height));
        ofile.write((char*)&width,  sizeof(width));
        ofile.write(buf, height * width);
        delete bitmaps[i];
static void bld_gfxlib(const char *file)
    if (count < bitmapcount)
        bitmaps[count++] = new PCXBitmap(file);

Listing Two

// ------------ cmdline.cpp
// Parse command lines for file names (no wildcards allowed
// in order to preserve file sequence control)
// Recognize a response file by @filename
// Call a callback function for each name parsed
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
#include <dir.h>
#include <io.h>
#include <string.h>
int checkfile(const char *fname)
    if (access(fname, 0) != 0)    {
        cerr << "\nNo such file as " << fname;
        return 0;
    return 1;
int process_responsefile(const char *rfname,
                             void (*func)(const char*))
    if (!checkfile(rfname))
        return 0;
    ifstream list(rfname);
    char fname[MAXPATH];
    while (!list.eof())    {
        if (*fname)    {
            if (!checkfile(fname))
                return 0;
    return 1;
int parse_cmdline(int argc, char *argv[],
                           void (*func)(const char*))
    int n = 0;
    while (argc--)    {
        if (*argv[n] == '@')    {
            if (!process_responsefile(argv[n]+1, func))
                return 0;
        else    {
            char path[MAXPATH];
            char drive[MAXDRIVE];
            char dir[MAXDIR];
            _splitpath(argv[n], drive, dir, 0, 0);
            _makepath(path, drive, dir, 0, 0);
            char *cp = path+strlen(path);
            ffblk ff;
            int ax = findfirst(argv[n], &ff, 0);
            if (ax == -1)
                return 0;
            do    {
                strcpy(cp, ff.ff_name);
                ax = findnext(&ff);
            } while (ax != -1);
    return 1;

Listing Three

// -------- bldmaze.cpp
#include <fstream.h>
int main(int argc, char* argv[])
    if (argc > 2)    {
        ifstream ifile(argv[1]);
        if (
            return 1;
        ofstream ofile(argv[2], ios::binary);
        if (
            return 1;
        unsigned char ch, holdch;
        while (!ifile.eof())    {
            char rlectr = 0;
            for (;;)    {
                if (ch != holdch)
            if (!ifile.eof())    {
                if (rlectr)
                    ofile.put((char)(rlectr | 0x80));
            holdch = ch;
    return 0;

Listing Four

// ---------- main.cpp
#include <time.h>
#include <iostream.h>
#include <stdlib.h>
#include <stdio.h>
#include <except.h>
#include "raycast.h"
#include "keyboard.h"
#include "sprite.h"
const int stepincrement = 4;  // maze coordinates per step
// ------ sprite class
class TubaPlayer : public Sprite    {
    SHORT xincr;
    SHORT yincr;
    SHORT stepctr;
    TubaPlayer(SHORT tx, SHORT ty);
    void StepForward();
    void AboutFace();
    SHORT CurrXIncrement() const
        { return xincr; }
TubaPlayer::TubaPlayer(SHORT tx, SHORT ty) :
                            Sprite('Z', "sprites.gfx")
    SetPosition(tx, ty);
    xincr = stepincrement;
    yincr = 0;
    stepctr = 0;
inline void TubaPlayer::StepForward()
    if (stepctr & 1)
inline void TubaPlayer::AboutFace()
    xincr = -xincr;
// ----------- game class
class TubasOfTerror : public RayCaster    {
    TubaPlayer* tp;
    SHORT spriteno;
    SHORT stepctr;
    TubasOfTerror(SHORT px, SHORT py, SHORT pangle,
                                         ViewPort vp);
    void StepSpriteForward();
    void SpriteAboutFace()
        { tp->AboutFace(); }
// ----- initial tuba player sprite position within maze
static SHORT tx1 = 14*64, ty1 = 28*64-16;
TubasOfTerror::TubasOfTerror(SHORT px, SHORT py, SHORT pangle,
                                ViewPort vp) :
          RayCaster("maze.dat", "tiles.gfx", px, py, pangle, vp)
    if (!isLoaded())
        throw("TILES.GFX load failure");
    tp = new TubaPlayer(tx1, ty1);
    if (!tp->isLoaded())    {
        throw("SPRITES.GFX load failure");
    spriteno = AddSprite(tp);
    stepctr = 0;
    delete tp;
// ----- walk the sprite through its path
void TubasOfTerror::StepSpriteForward()
    if (stepctr == 20 * (tilewidth / stepincrement))
    if (stepctr == 40 * (tilewidth / stepincrement))    {
        stepctr = 0;
    MoveSpriteRelative(spriteno, tp->CurrXIncrement(), 0);
// ---- view ports: changed by pressing + and -
static ViewPort vps[] = {
//      x   y   ht   wd   (position and size)
//    ---  --  ---  ---      
    { 120, 75,  80,  50 },
    { 110, 69, 100,  62 },
    { 100, 62, 120,  74 },
    {  90, 57, 140,  86 },
    {  80, 50, 160, 100 },
    {  70, 52, 180, 112 },
    {  60, 45, 200, 124 },
    {  50, 37, 220, 136 },
    {  40, 30, 240, 150 },
    {  30, 23, 260, 164 },
    {  20, 15, 280, 174 },
    {  40, 30, 240, 120 },    // typical
    {   0,  0, 320, 200 },    // full screen
const int nbrvps = sizeof vps / sizeof(ViewPort);
int vpctr = nbrvps - 2;  // viewport subscript
int main()
    TubasOfTerror* ttp = 0;
    // ----- player's starting position and view angle
    SHORT x = 2163;
    SHORT y = 1730;
    SHORT angle = 180;
    // ---- for computing frames/per/second
    long framect = 0;
    clock_t start = clock();
    // ---- error message to catch
    char* errcatch = 0;
    try    {
        // ----- ray caster object
        ttp = new TubasOfTerror(x, y, angle, vps[vpctr]);
        // ---- keyboard object
        Keyboard kb;
        while (!kb.wasPressed(esckey))    {
            // ----- draw a frame
            // ----- test for player movement commands
            if (kb.isKeyDown(uparrow))
            if (kb.isKeyDown(dnarrow))
            if (kb.isKeyDown(rtarrow))    {
                if (kb.isKeyDown(altkey))
            if (kb.isKeyDown(lfarrow))    {
                if (kb.isKeyDown(altkey))
            // -------- open and close door commands
            if (kb.wasPressed(' '))
            // ----- command to turn the map on and off
            if (kb.wasPressed(inskey))
            // ----- commands to change player movement speed
            if (kb.wasPressed('f'))
            if (kb.wasPressed('s'))
            // ----- commands to change the size of the viewport
            if (kb.wasPressed(pluskey))    {
                if (vpctr < nbrvps-1)    {
                    ttp->GetPosition(x, y, angle);
                    delete ttp;
                    ttp = new TubasOfTerror(x, y, angle,
            if (kb.wasPressed(minuskey))    {
                if (vpctr > 0)    {
                    ttp->GetPosition(x, y, angle);
                    delete ttp;
                    ttp = new TubasOfTerror(x, y, angle,
            // ----- walk the sprite
    catch (char* errmsg)    {
        errcatch = errmsg;
    catch (xalloc xa)    {
        static char msg[50];
        sprintf(msg, "Out of memory (%d)", xa.requested());
        errcatch = msg;
    // ---- get report player's final position
    if (ttp)
        ttp->GetPosition(x, y, angle);
    // --------- get current time to compute frame rate
    clock_t stop = clock();
    delete ttp;
    if (errcatch)
        cerr << "\aRuntime error: " << errcatch << endl;
    else    {
        cout << "-------------------------------" << endl;
        cout << "Frames/sec: "
             <<  (int) ((CLK_TCK*framect)/(stop-start)) << endl;
        cout << "-------------------------------" << endl;
        cout << "Position (x, y, angle) :"
             << x << ' ' << y << ' ' << angle << endl;
    return 0;

Copyright © 1995, Dr. Dobb's Journal

Related Reading

More Insights

Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.