Programming with Allegro:

Most of the source code from this site is from Johnathon Harbour's Game Programming All in One, 2nd Ed., Thomson Course Technology, Boston, 2005.

Basic 2-D Graphics

Accessing system information via Allegro: getinfo.c. Note use of #include "allegro.h", the call to allegro_init() and END_OF_MAIN(). The last is a macro that takes care of win_main stuff on Microsoft platforms. The readkey() function pulls a character from STDIN.

Our first exposure to the graphics capabilities of Allegro is in helloworld.c. The gfx_mode call sets the screen resolution. There are many options for the mode. The GFX_SAFE provides for a window of the specified dimension, rather than trying to force the entire screen to that, which might result in an error (in which case check allegro_error). The final two args are v_w and v_h which are used to specify a larger virtual screen for use with hardware scrolling and page flipping.

The textout call makes use of two of Allegro's global variables, screen and font. The other arguments are the x-y coordinates as well as a color index. (Use makecol() to create other colors.) The index is into the current (256) color table. In the default table, colors 0-15 are basic.

Here's a simple program to draw lots of lines, lines.c. Notice the use of line and the makecol functions. Also note the use of the global key array to determine when a key has been pressed. The array is constantly updated. (Consequently, it is possible for a key to be pressed and released before your program tests the array value!)

The dolines.c program demonstrates the use of callback functions by Allegro. The user provides a pointer to a function that is to draw each of the pixels comprising a line.

There are functions for drawing rectangles and filled rectangles (rect and rectfill) as well as circle and ellipses (circle, circlefill, ellipse and ellipsefill). The spline function takes an array of 8 integers, acting as 4 x-y pairs and renders a smooth curve that passes through those points. The polygon function takes an array of 2v integers, representing the coordinates of the v vertices defining the polygon, as demonstrated in polygons.c.

The floodfill.c program demonstrates the use of floodfilling as well as a simple animation (a bouncing ball).

There are two basic functions for text output: textout, which print to a given x-y coordinate, and textprintf, which does the same thing but allows for formatting using C's printf syntax. There are also methods to left, right, and center justify text. The program textoutput.c demonstrates this.

A First Game: Tankwar

The code of tankwar.c and tankwar.h illustrate many of the basic components of an interactive game. Start by looking at the main method. Notice the initialization code. The install_timer code is necessary to provide platform-independent access to system clocks and timers. This is used in the rest function later. The keypressed function is a non-blocking call to see if the keyboard input buffer has anything. (Note that readkey blocks until input if found.)

num is used as an index to select from among the two tanks. The tanks and their bullets are represented by structs (see tankwar.h.)

The rest function yields the CPU to the OS, so you don't know for sure when you might get more cycles. Still, this is a good idea on most platforms, unless you like listening to the fan!

To be honest, the code, here, is pretty ugly. A number of software engineering rules are flagrantly violated. How many can you identify? How would you improve the code?

Programming the Keyboard and Mouse

The install_keyboard call sets up a keyboard event handler that sets the values in the global key array. (See allegro.h for the large number of KEY_... constants to be used as indices into the array. Note that the "A" key generates two keystrokes, KEY_SHIFT and KEY_A. The poll_keyboard function can be used if the operating system does not support events.

For buffered key input use readkey or ureadkey (for unicode) . The set_keyboard_rate controls the rate at which keys are generated when a key is held down. The global key_shifts can be used to dynamically query which meta keys are held down. E.g:

if ((key_shifts & KB_CTRL_FLAG) && (readkey() == 13))

If you want you can simulate key presses (perhaps to demo or test your program) via the simulate_keypress function.

Mouse input

As with the keyboard, Allegro provides both poll- and event-based interaction with the mouse. The poll_mouse and mouse_needs_poll functions can be used together for poll-based interaction.

extern volatile int mouse_x, mouse_y, mouse_z, mouse_b

The first 3 variables provide position, and access to the mouse wheel. The last is accessed to determine which buttons have been pushed:

if (mouse_b & 1) // left mouse button pushed. 2 for right, 4 for center.

The set_mouse_sprite function is used to change the appearance of the mouse. The function set_mouse_sprite_focus redefines the "center" of the sprite. The sprite is treated as "transparent". I.e., one of the colors is treated as transparent. Use the show_mouse function with double-buffering to control to where the cursor will be rendered. scare_mouse and unscare_mouse are used to turn the cursor off and on. The program strategicCommand.c illustrates the use of the mouse. You'll also need the bitmaps city.bmp and crosshair.bmp.

The function position_mouse let's you move the cursor. Its use, as well as a sprite's, is demonstrated in positionMouse.c and spaceship.bmp.

If you want to use the mouse as in a first person shooter, you'll want relative movement. For that, use get_mouse_mickeys.

void get_mouse_mickeys(int *deltax, int *deltay)

Timers and Threads

There are two many goals here: prevent games from running too quickly on faster CPUs, prevent games from running too slowly on slow CPUs. Let's tackle the second problem first. We should have a target frame rate of between 30 and 60. (More is okay.) A problem arises when the amount of work, including drawing operations, rendering single frame is greater than 1/30th of a second. We don't want the game speed to slow down in this case. An example will help: suppose it takes 1/10 of a second to render the graphics of a single frame of animation, but it takes virtually no time to calculate the positions of the objects within each frame. If our game loop looked like this:

loop:
  updateObjectPositions();
  renderObjects();
  

..then the position of our objects would change every 10th of a second. If we then moved to a faster platform where the frame rendering took only 1/30th of a second, the game would run three times faster!

To solve this problem we want to invoke the updateObjectPositions() method three times per iteration on the slow platform. That way, even though the frames are redrawn only every 1/10 of a second, the objects will have moved as much as they would have moved in 1/10 of a second on the faster machine.

To solve this problem we can use a timer to increment a counter at a fixed interval, say once every 30th of a second. In our main loop, we maintain a second counter. If the second counter is out of synch with the first, we can iterate over the updateObjectPositions() enough times to make up for the delay. The program from the Allegro Vicace tutorial demonstrates this:

0943.c 0943.h 0943i.c 0943i.h

Note the use of Allegro's LOCK_VARIABLE, LOCK_FUNCTION, END_OF_FUNCTION macros. (The use syntax for the last of these is particularly odd.) These assure that a virtual memory OS will never swap this code or these variables out of memory. Note, too, the use of the "volatile" keyword, which precludes the compiler from optimizing these variables into register or cache memory. This is to prevent problems that can occur when an interrupt handler modifies data values shared by other threads.

A word about interrupt handlers: Allegro provides for up to 16, but needs some of those for itself. The code in these should be very brief, and all variables that are changed therein must be protected by LOCK_VARIABLE calls. The handlers, and any functions that they invoke should be protected by a LOCK_FUNCTION call (which, in turn, requires the use of the END_OF_FUNCTION macros.)

The BPS_TO_TIMER macro indicates how many hardware clock ticks will occur between each frame if the given number of frames per second is to be achieved. The Allegro clock should tick 1193181 times per second. Thus, 1193 ticks equals 1 msec. (These figures are important if you use the install_int_ex functions.)

To use the 0943 program, note the speed of the bouncing ball when the program first starts, along with the frame rate. Now use the + on the keypad to increase the number of pixels being drawn each frame. When this gets very high, you will see the frame rate has decreased below 60fps. But the ball appears to be moving at the same speed. This is because the ball's position is still being modified 60 times per second, even if the ball is only be drawing at the reduced frame rate.

Okay, the second problem is how to keep the game from running too quickly on a fast platform. The 0943 program does this by busy waiting: the frames are drawn as quickly as possible, but the ball's position in only updated at most once per 60th of a second (see game_loop). This works, but means that the CPU's use will be maxed out. Instead, it is better to yield control to the CPU until a frame needs to be redrawn.

The timedloop program from Harbour's book illustrates a technique:

main.c .

Allegro's rest_callback(n, fnc) rests for n msecs, repeatedly calling *fnc until that time has elapsed. This code relays on a hard-coded number of milliseconds to wait between each game loop. A better way would be to calculate this value dynamically, in response to the values of counters such as in the 0943 program.

Full documentation for Allegro's timer routines is on their site.

Bitmap Manipulation

Just about everything drawn in a game is sprites. Each of these, including the background, is encoded as a bitmap. Allegro has a BITMAP struct to represent these. The global screen is a pointer to the screen's current bitmap.

extern BITMAP *screen

(See allegro.h if you really want to know the insides of the data structure.) The function for generating a new bitmap is simple enough:

BITMAP *create_bitmap(int w, int h)

which creates a bitmap with the current color depth. Use the _ex version of this function if you want to use a different color depth. To clear a bitmap quickly, use

void clear_bitmap(BITMAP *bmap)

Or the clear_to_color function to clear to something other than 0 (black).

We can create sub bitmaps of other bitmaps. The interesting aspect here is that the two continue to share the same underlying memory, so changes to one can affect the other. This can be particularly handy if you want to alter only a portion of the screen bitmap.

BITMAP *create_sub_bitmap(BITMAP *parent, int x, y, w, h)

Use destroy_bitmap to free the allocated bitmap structures. There are various functions to gather information about bitmaps (is_screen_bitmap, e.g.). See the documentation for these.

When Allegro draws to a bitmap, it must first lock the bitmap, which precludes any other process from altering it. This involves a system call, so can be rather slow. If the game is going to effect several drawing operations on a bitmap, it pays to lock it just once, do the operations, then unlock it. Your friends are:

void acquire_bitmap(BITMAP *bmp), acquire_screen()

Along with their corresponding release_ partners.

You can restrict the clipping rectangle within a bitmap via

void set_clip(BITMAP *bmp, int x1, y1, x2, y2)

The most common way to load bitmaps is from a file. Check the documentation for a number of functions that allow this from various types of image files. In addition, there are several save_ operations that allow the dumping of an existing bitmap to an external file.

Blitting:

The basic blitting functions are

void blit(BITMAP *src, *dest, int srcX, srcY, destX, destY, w, h)
void stretch_blit(BITMAP *src, *dest, int srcX, srcY, srcW, srcH, destX, destY, destW, destH)

The masked blit operation does not overwrite the destination pixels where the source pixels are pink (255, 0, 255).

This version of tankwar.c generates bitmaps during initialization which are then used throughout the game: main.c tankwar.h .

Basic Sprite Programming:

We can just use the blit operators to paint sprites. Allegro provides a number of functions, though, that provide more advanced capabilities, including translucency, lighting effects, etc. The basic drawing operation assumes the default transparency color (255,0,255) in the sprite bitmap:

void draw_sprite(BITMAP *src, *sprite, int x, y)

The assumption is that the entire sprite will be rendered, so there is no w or h arguments. Usually multiple sprite images are encoded in a single external file (a larger bitmap). The sprites are then sub bitmaps of the larger map. The program drawsprite ( main) illustrates the use of this function. If you change the color depth from 16 to 8 the transparency effect will be lost.

The stretch_sprite function does the same thing, but takes an additional w and h argument to alter the size of the sprite. The ScaledSprite program main demonstrates this. There are also functions for flipping and rotating sprites.

void rotate_sprite(BITMAP *src, *sprite, int x, y, fixed angle)

The rotation argument is weird, because the value is not in degrees, but in a value from 0 to 255--a normalization of the degrees. If d is the degrees, the normalized value is 255*((d%360)/360). Basically, every 45 degrees is a rotation of 32. Initialization code often loads a sprite, then use the rotation operation to precalculate a set of sprites that represent the original in various rotations. The rotateSprite program main demonstrates this. There is also a pivot_sprite function that provides for a different rotation axis other than the center of the bitmap. See the pivotSprite program main here.

Allegro provides for translucency via alpha blending. (See set_alpha_blender.)This technique is quite tricky via Allegro, but is possible. You'll want to stay away from it if your platform's video card doesn't support it.

Here is a version of the tankWar program, main tankwar, but with obstacles removed and the tanks replaced with 8-way rotated bitmaps. Pay particular attention to the new explode, updatebullet and fireweapon methods. The setuptanks method is also very different from the older versions, because it dynamically generates rotated bitmaps.

Animated Sprites:

The simplest way to accomplish an animated sprite is to represent each such sprite with an array of sprites, each representing a different frame of a short animation of that sprite. Associate a frame counter with each animated sprite which indicates which element of the array will be used to render the sprite. Manipulate this counter to achieve the desired animation. The animsprite program ( main ) demonstrates this.

Usually, the animation cells are stored in a single bitmap, and the various sprites comprising the animation are sub bitmaps. The SpriteGrabber program main(again, from Harbour) demonstrates the use of this technique, plus the use of a SPRITE struct to encapsulate the information of the animated sprite.

From here it is relatively straight-forward to animate several sprites simultaneously. In this example, each sprite is a separate animation consisting of the same set of animation frames. Each sprite's current frame position can differ from other sprites' (see the curframe field of the SPRITE struct. Note that each sprite has its own framedelay variable.) Each sprite also has its own velocity and frame delay. Each sprite is effectively a copy of the same movie, but each is being shown at a different rate. Here is the code for multiplesprites ( main).

So far we've looked at the simplest kind of sprite, a raw bit image. If memory is a problem, we may be able to represent the animation cells as run-length encode sprites. Allegro has a number of RLE_ operations to support this data structure. Note that no image manipulation operations (flipping, stretching, etc.) are possible on RLE_SPRITES. Rendering can sometimes be faster if the bitmaps contain lots of "transparent" pixels that can be skipped during the decompression process.

Allegro also supports compiled sprites. These require quite a bit more memory, as each sprite is represented not as an array of pixels values, but as machine code, which, when executed, renders the image. See Allegro's COMPILED_SPRITE data strucutre and related _compiled_sprite functions. Here is the code for compiledSprite ( main).

Simple Collision Detection:

Harbour's collisionTest program ( main)generates a number of animated billiard balls that bounce off of each other and the sides of the window. Each collision changes the sign of the velocity of the ball, but may also change its magnitude, so "virtual momentum" is not necessarily conserved. This is easy to observe if you concentrate on following just one of the balls. The collision algorithm uses a square bounding box around each ball that is 10 pixels smaller than a "true" bounding box. The smaller box decreases the likelihood of false positives with the collision test, though it does mean that the ball images sometimes overlap slightly, if only briefly. The test itself merely checks to see if any of the vertices of one bounding box are inside the other. (See the collided function.)

Here's another version of the tankwar program(). It makes use of sprites for animation. I've also added code to detect when the tanks collide.

Scrolling Tile-Based Terrains :

The basic idea behind scrolling is simple. We display only a part of a larger image that falls within a virtual window, called a viewport. Scrolling simply moves the virtual window across the larger image. In scrollTest (main), the program first generates a large bitmap, and initializes two variables (x,y) to keep track of the current "position" within the map. The screen bitmap is init'd to show the upper-left hand portion of the larger bitmap. The arrow keys are used to change x and y and thus change that portion of the larger bitmap that is displayed.

As virtual terrains become large, it becomes infeasible, or at least unwieldly, to store the terrain as a single, very large bitmap. Instead, terrains are constructed as a mosaic of tiles. We might several tiles to represent forest, several to represent roads, etc. The terrain becomes a grid, each cell of which is a reference/pointer to one of the underlying tile types. The tileScroll program ( main)generates the underlying terrain map dynamically: randomly blitting terrain tiles onto a large bitmap. (See "draw tiles randomly.)

Well, random terrain isn't very interesting. What is needed is a way to assign particular tiles to particul locations on the terrain grid. The GameWorld program ( main) does this via the map variable, a one-d array masquarading as a 2-d grid, whose elements are indices (0-39) to an array of terrain cells. If you look at the terrain cells you can see that the last cell is all black. This can be used to render a border, if desired.

Putting all this together, we get a version of TankWar ( bullet, input.c, main.c, map.h, setup, tank, tankwar, ) that features terrain tiles and two scrolling windows, one for each player, as well as a zoomed out "radar" view that shows the entire map. Again, a map array is used to define the terrain in terms of tiles. Note the use of border tiles ('B') to define the terrain edge. The program has finally been decomposed into several smaller files. bullet.c, for example, contains all the code for rendering and manipulating the bullets. Note that when a bullet is drawn (drawbullet), it is actually rendered in both scrolling windows. Note, too, that there is no longer an erase function. This is because the scrolling windows redraw their entire content with each frame.

A drawback to this way of handling scrollable terrain is that we are still memory-restricted for the size of the underlying bitmap. A better solution in such a case is to form the underlying bitmap dynamically--just make sure that the portion of the terrain map that has been formed from tiles is bigger than the viewport.

Adding Timer Animation to Tankwar:

There are several changes in this version of Tankwar( bullet input main map setup tank tankwar). First, the explosion code no longer suspends the game while the explosion is being animated. Note the changes in tankwar.h involving the new explosionsarray, as well as the tank_bmp array (we've added another dimension). Also note the introduction of three functions: animatetank, updateexplosion and loadsprites. The main changes to setup.c revolve around loading the animation frames for the tank movement. The main changes to bullet.c are that explosions are drawing a single-frame at a time in updateexplosion. This function, in turn, is invoked once per game loop.

Threads:

There are several implementations of posix threads on most platforms, including Windows, MacOS and Unix/Linux. Their use is not as simple as threads in Java, but is sufficient. Here's a quick overview. For a more in depth treatment, see on of the many tutorials on-line.

To create a new thread:

int pthread_create(pthread_t *threadID, // a return param
   const pthread_attr_t *attr, // usually, put NULL here
   void *(*start) (void *), // The callback function the thread will invoke
   void *arg);  // An array containing any parameters to that function

 Here's an example

int id; pthread_t pthread0; int threadID0=0;
id = pthread_create(&pthread0, NULL, thread0, (void*)&threadid0);

void* thread0(void* data) {
  int my_thread_id = *((int*)data;
  while (!done) {
    // do something marvelous!
  }
  pthread_exit(NULL);
  return NULL;
}

Now comes the tricky part of threads: protecting against simultaneous access to data. For example, we don't want one thread writing to a buffer while another thread is blitting it to the screen. Posix provides a data structure called a mutex to avoid this situation. These act much like binary semaphores, if you are familiar with that concept. A thread attempting to lock a mutex that has already been locked by another thread will block until the lock is released (unlocked). Here's some code demonstrating this:

pthread_mutex_t mySemaphore = PTHREAD_MUTEX_INITIALIZER; // creates a mutex struct
...
 pthread_mutex_lock(&mySemaphore);  // Try to obtain a lock
...
 pthread_mutex_unlock(&mySemaphore);  // Relase the lock.
...
pthread_mutex_destroy(&mySemaphore);  // Must destroy before exiting!!

Okay, with that done, here is a demo program, MultiThread ( main). In the code, thread0 is moving one ball, while thread1 moves the other. The threads share a single mutex to control access to their shared buffer. Because each thread is loop containing a rest() call outside the use of the mutex, there is ample opportunity for each thread to work. There should be no threat of starvation, or deadlock. The demo uses two different functions, one for each thread, but this wasn't really necessary. Each thread could have used the same callback function and made use of the argument to tell which thread was executing the code.

Terrain Editing: Mappy

You can download mappy at www.tilemap.co.uk. Start Mappy, create a new map. Then import a bitmap defining a bunch of tiles, such as maptiles.bmp. From the Custom menu you can use the Solid Rectangle script to fill the entire map with one "default" tile. Mappy can create grid, hexagonal and isometric maps. It is also capable of some dynamic terrain elements. Once you've made a map, you can save the map in .fmp format. This file encapsulates both the map file and the tile images. Do that first, then we'll use mappy to generate code that will load the tile images to create a map. Use File:Export... From the resulting dialog box, select "Graphics Blocks as picture .BMP" and "Map array as comma values only .CSV". This will generate two files, a .BMP and .CSV file. Change the .csv extension to .txt and edit it to see what you've got. Take a look!

Okay, once you've generated your map and pasted the .csv text into your .c or .h file in the appropriate places, you're ready to go. See ArrayMapTest program ( main)for an example.

You can also make use of the MappyAL library to access the .fmp file directly, and avoid a lot of this rigamarole. I leave it to you to work this out from the documentation at the Mappy web site. (Basically you download the Mappy .zip file and extract the mappyal.c and .h file and integrate them into your program. While the mappyAL API makes it a lot easier to load the maps, it is restricted to only work with 256-color bitmaps.)

A Vertical Scroller

The Harbour book comes with a nice vertical scroller demo. This demo allows the player to scroll up and down across a very large map. The map has been created with Mappy, and is the maximum size allowable by Mappy: 640 x 48,000 pixels. I.e., about 32K tiles. Each tile, in this case, is 32x32 pixels. The code is quite small because most of the scrolling work is done by Mappy's API (see, especially, MapDrawBG and MapLoad). The map provided with the demo was generated with Mappy. The "islands" were generated by a Lua script run inside Mappy.

The code (here's just the driver, main.c.) illustrates the major components of implementing a full vertical-scroller game. This game also uses a Mappy .fmp file to store the map.

A Horizontal Scroller

Harbour provides another nice demo, this one of a horizontal scroller, in the mold of Super Mario.... The major topic introduced by the coding of demo is a more advanced use of Mappy to provide collision detection and background maps. When you run the demo you will see a single character controlled by the left & right arrow keys as well as the space bar (for jumping). The character falls when walking off of blocks and can jump onto higher blocks. There is a background pattern that scrolls by as the character moves to the left or right. The background does not scroll vertically when the character jumps or falls, however.

First, obtain or generate a bitmap image 640x480 pixels, X. This will represent the background of the scroller. You should try to engineer the bitmap so that its left and right edges appear to flow into each other. Here's an example of such an image. Now, using your bitmap editor, create a new bitmap, Y, combining X with the bitmap of the terrain blocks. The combined bitmap should have X at the top and the image of the terrain blocks below. Leave some empty space at the bottom of the image if you want to be able to add new terrain tiles later. If you want, you can use this sample.

Now start Mappy to set up the background and terrain tiles. First, run Mappy and create an empty 640x480 pixel map, with each tile being 32x32, and 16 bit color. Now, back in Mappy, use the MapTools->Useful Functions->Create Map from Big Picture menu item and select Y. The result will be a bunch of tiles, which, when laid out as a mosaic will form the original background image.

Now, because we want the map to be quite a lot larger, use the MapTools->Resize Map command to change the size of the map to be quite a bit larger horizontally (and vertically insofar as you want to accomodate vertical scrolling.) I chose 1000 tiles by 15, but you could choose anything so long as the total number of tiles is < 30,000.

This leaves quite a lot of black (empty) space to the right of our original background. Rather than painstakingly painting each of those, we'll replicate the background repeatedly to the right until filling the entire map. To do that, use the Brush menu to create a "brush" that consists of the entire background map. Note that the resulting map structure uses very little memory, since the entire map is formed from only several dozen 32x32 tiles.

Terrain Blocks

Next, to enable collision detection via Mappy we need to label the terrain tiles as "foreground" rather than "background" tiles. We also want to makr each of the terrain tiles as being involved in collision detection. This is somewhat difficult to explain, so just look at this Flash movie I've made. We can actually perform this manuever on all the tiles we want simultaneously by using the MapEdit->Range Edit Blocks command. Here's a movie demonstrating how to do that.

The program handles collision detection by checking the where the character is currently located via the mappyal API. Mappy encodes a simple struct, BLKSTR, with each tile. Of particular interest to us are the four unsigned char fields named tl, tr, bl and br. Their value will be 0 or 1, indicating whether we checked the corresponding collision box in Mappy. These represent the top-left, top-right, bottom-left and bottom-right corners of the cell. Given the (x,y) pixel coordinates of the character, it is easy to check if the character is over a tile for which collision is enabled:

int collided(int x, int y)
{
    BLKSTR *blockdata;
	blockdata = MapGetBlock(x/mapblockwidth, y/mapblockheight);
	return blockdata->tl;
}

Harbour's mechanism for handling jumps and drops is somewhat cryptic. I leave you to examine the code to determine how this works. Pay particular attention to the variable jump and the constant JUMPIT.

Sound

Allegro's sounds capabilities are somewhat limited, but certainly up to the task of handling the sound for most traditional game genres. Allegro works well with .wav and .voc (from Creative Labs, manufacturers of lots of sound card drivers) files, so you'll probably want to use those. The mechanism for handling these is fairly easy. First, install a digital sound driver. All modern sound cards have their own already, so you probably just want to allow Allegro to use the card's default:

    //install a digital sound driver
    if (install_sound(DIGI_AUTODETECT, MIDI_NONE, "") != 0) 
    {
        allegro_message("Error initializing sound system");
        return;
    }
 

If you wanted to provide MIDI music or something like that, then you'd need to vary the arguments to install_sound.

Next, load the .wav file into a SAMPLE object. When that is done, it can be played via the play_sample function. Here's some code that does this:

    //load the wave file

    int panning = 128;
int pitch = 1000;
int volume = 128;
SAMPLE *sample; sample = load_sample("clapping.wav"); if (!sample) { allegro_message("Error reading wave file"); return; } //play the sample with looping play_sample(sample, volume, panning, pitch, TRUE);

The pitch refers to the number of samples per second used to create the .wav file. The volume and panning arguments vary from 0 to 255. Panning provides a stereo effect: 0 is all left, and 255 is all right. Volume is silent at 0 and maximum at 255. You want to be careful with the volumes if you'll be playing more than one sounds at a time. Try to keep the sum below 256 to avoid distortion, though I think most sound cards handle that on their own these days. The fifth argument indicates that the sound should loop. The adjust_sample function that alters the sound parameters has affect only while the sound is playing.

The playWave program (along with clapping.wav) demonstrates the result of manipulating the panning, pitch and volume during playback.

The sampleMixer program demonstrates the result of overdubbing several voices.

Allegro has lots of low-level sample playback routines, which allow much tighter control. These might be useful if you want to blend lots of sounds simultaneously. In that case you'll need to reserve an appropriate number of voices, assign samples to each voice, perhaps fiddling with their pitch, volume, etc. You can also provide for volume ramping, frequency sweeps ("glissando", for you piano buffs out there) and sweeping pans (a la Pink Floyd's "On the Run").

Tank War c'est Fini!

Here's the last installment of Tank War. This one has sound files added. Perhaps more interestingly, Harbour has removed the rest() that formerly provided the delays to slow the game down to a manageable level. This has been replaced with a key_count and key_delay variables that combine to limit how fast the key strokes can be input, thus avoiding unintentionally doubled keystrokes. Please examine the code to see the many changes. (There's also a certain amount of code added to handle joysticks, but you don't see those around much anymore these days...)