\chapter{Finishing Penguin Warrior}
\label{ch:finishingpw}

Over the course of this book, we have developed a small Linux game called \gametitle{Penguin Warrior}. Right now, however, it lacks many of the features that would make it an enjoyable game. Players can fly around a small world, but there's not much else to do. In this chapter we will turn \gametitle{Penguin Warrior} into a complete, playable video game. Most of the machinery is in place already, so this will be a rather short discussion. In the process of finishing \gametitle{Penguin Warrior}, we'll touch on some topics that don't really fit anywhere else. We'll start by adding weapons to the game.

\section{Adding Weapons}

What's a parallaxing shooter without weapons? The ships in \gametitle{Penguin Warrior} need some phaser cannons. We've already added the requisite support to the network and audio subsystems, so this feature won't be difficult to add. We'll need the following:

\begin{itemize}
\item A routine to draw a phaser beam to the framebuffer. We'll use a beam weapon rather than a projectile weapon. Beams look neat, and they require less bookkeeping than projectiles.
\item Collision detection code to determine whether a phaser beam hits its target. This will involve solving a quadratic equation for the distance between the ray and the center of the target. If this distance is less than the approximate radius of the object, the routine will report a hit.
\item Code to limit each player's rate of fire. We obviously don't want players to sit in one place and hold down the fire button for a continuous revolving death ray. It's annoying enough when robots do this in the game \gametitle{MindRover}; we won't allow this in \gametitle{Penguin Warrior}.
\end{itemize}

We'll address each of these issues in turn.

\subsection{Drawing Phasers}

%Author: Below, why call Star Trek a documentary? Was this some sort of documentary about Star Trek? I think this needs to be explained a bit more.
% It's a joke based on Galaxy Quest. /jh

For our purposes, a phaser beam is a ray (that is, a line that starts at a certain point and continues forever in a particular direction).\footnote{The television documentary \othertitle{Star Trek} tells us that a phaser is really a special type of particle beam, but we'll forgo that bit of realism for now.} To draw these Penguin phaser beams, we'll need a general-purpose line-drawing routine.

\index{lines|(}
How should we go about drawing a line between two points on the framebuffer? It may not seem like a difficult problem at a glance, but doing it quickly is a bit of a trick. From the given starting point, we need to iterate across the framebuffer in steps so that we land exactly at the ending coordinate. At each step, we draw one pixel. The challenge is to do this without drawing the same point more than once and without leaving holes in the line. One possible algorithm is as follows:

\begin{itemize}
\item Determine which axis has the larger span. This is the \firstterm{major} axis, and the other is the \firstterm{minor} axis. A mostly vertical line, for instance, has a vertical major axis and a horizontal minor axis.
\item Calculate the ratio $r = \frac{minor}{major}$, accounting for the special case where the major axis span is zero.
\item Iterate from the starting point to the ending point. For each step, move one pixel in the major direction and $r$ pixels in the minor direction. ($r$ will be less than 1, so this needs to be done with floating-point math.) Plot a pixel at each step.
\end{itemize}

This algorithm works (code it up if you wish), but it's not very fast. Performance might be acceptable if you replace the floating-point math with fixed point (a useful technique that we leave to other sources), but there's a better way to draw lines.

Let's analyze our algorithm. At each step, we move one pixel in the major direction, and $r = \frac{minorspan}{majorspan}$ pixels in the minor direction. The method we've just outlined calculates $r$ at the start of the routine and repeatedly adds it to the coordinate of the minor axis. This floating-point operation is one of the major costs of the algorithm, and we'd like to get rid of it if at all possible.\footnote{Actually, the addition is rather cheap on today's processors. The killer is the inevitable conversion of the floating-point coordinate to an integer at each step. To be perfectly honest, today's processors are fast enough that you could get away with using this algorithm in a game. But we can do better.}

To optimize this algorithm out of the loop, we need to look at the nature of division. Suppose you want to divide 15 by 4. An obvious approach is to simply add up 4s until you break 15, then take 1 less than that number as your answer (which in this case is 3, with a remainder of 3). This is called \firstterm{incremental division}, and we can leverage it for faster line drawing.

At each step along the line's major axis, we'll perform one step of incremental division by adding the line's minor span to a running total. When this total exceeds the span of the major axis, we'll take one step along the minor axis and subtract the major span from the total. With variables, the algorithm looks something like this:

\begin{enumerate}
\item Set $x$ and $y$ to the starting coordinates, $major$ and $minor$ to the major and minor spans, and $sum$ to 0.
\item Draw a pixel at ($x$, $y$).
\item Add 1 to the coordinate of the major axis ($x$ or $y$).
\item Add $minor$ to $sum$.
\item If $sum \geq major$, add 1 to the coordinate of the minor axis ($x$ or $y$) and subtract $major$ from $sum$. (By subtracting $major$ instead of setting $sum$ to 0, we preserve the remainder. This is important.)
\item If ($x$, $y$) has reached the final coordinate, plot the last pixel and exit. Otherwise, go back to step 2.
\end{enumerate}

This is called the \index{lines!Bresenham algorithm}\firstterm{Bresenham line algorithm}, and variants of this algorithm are in wide use today. The key advantage of this algorithm is that it uses purely integer math and thereby avoids costly floating-point-to-integer conversions. Other texts frequently present this algorithm at more of a conceptual level (labeling $sum$ as $error$ and describing it as the deviation of the plotted pixels from the true path of the line), but I find these descriptions somewhat confusing.

Listing \ref{code:bresline} implements the Bresenham algorithm with SDL.

\begin{codelisting}{bresline.c}{code:bresline}
\begin{verbatim}
/* Example of Bresenham line drawing with SDL. */

#include <SDL/SDL.h>
#include <stdlib.h>
#include <stdio.h>

/* Draws a line of the given color on surf from (x0, y0)
   to (x1, y1). Does not perform clipping against the
   edges of the surface.
   Uses the Bresenham line drawing algorithm. */
void DrawLine16(SDL_Surface *surf, int x0, int y0,
                int x1, int y1, Uint16 color)
{
    Uint16 *buffer;
    int drawpos;
    int xspan, yspan;
    int xinc, yinc;
    int sum;
    int i;

    /* If we need to lock this surface before drawing
       pixels, do so. */
    if (SDL_MUSTLOCK(surf)) {
        if (SDL_LockSurface(surf) < 0) {
            printf("Error locking surface: %s\n",
                   SDL_GetError());
            abort();
        }
    }

    /* Get the surface's data pointer. */
    buffer = (Uint16 *)surf->pixels;
        
    /* Calculate the x and y spans of the line. */
    xspan = x1-x0+1;
    yspan = y1-y0+1;
        
    /* Figure out the correct increment for the major axis.
       Account for negative spans (x1 < x0, for instance). */
    if (xspan < 0) {
        xinc = -1;
        xspan = -xspan;
    } else xinc = 1;

    if (yspan < 0) {
        yinc = -surf->pitch/2;
        yspan = -yspan;
    } else yinc = surf->pitch/2;
        
    i = 0;
    sum = 0;
        
    /* This is our current offset into the buffer. We use this
       variable so that we don't have to calculate the offset at
       each step; we simply increment this by the correct amount.

       Instead of adding 1 to the x coordinate, we add one to drawpos.
       Instead of adding 1 to the y coordinate, we add the surface's
       pitch (scanline width) to drawpos. */
    drawpos = surf->pitch/2 * y0 + x0;
        

    /* Our loop will be different depending on the
       major axis. */
    if (xspan < yspan) {

        /* Loop through each pixel along the major axis. */
        for (i = 0; i < yspan; i++) {

            /* Draw the pixel. */
            buffer[drawpos] = color;

            /* Update the incremental division. */
            sum += xspan;

            /* If we've reached the dividend, advance
               and reset. */
            if (sum >= yspan) {
                drawpos += xinc;
                sum -= yspan;
            }

            /* Increment the drawing position. */
            drawpos += yinc;
        }

    } else {

        /* See comments above. This code is equivalent. */
        for (i = 0; i < xspan; i++) {

            buffer[drawpos] = color;
                        
            sum += yspan;

            if (sum >= xspan) {
                drawpos += yinc;
                sum -= xspan;
            }

            drawpos += xinc;
        }
    }

    /* Unlock the surface. */
    SDL_UnlockSurface(surf);
}

int main()
{
    SDL_Surface *screen;

    /* Fire up SDL. */
    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
        printf("Error initializing SDL: %s\n", SDL_GetError());
        return 1;
    }
    atexit(SDL_Quit);
        
    /* Set a 640x480 video mode. Force 16-bit hicolor. */
    if ((screen = SDL_SetVideoMode(640, 480, 16, 0)) == NULL) {
        printf("Error setting a 640x480, 16-bit video mode: %s\n",
               SDL_GetError());
        return 1;
    }
        
    /* Draw a diagonal line across the screen. */
    DrawLine16(screen, 0, 0, 639, 479, 0xFFFF);
    SDL_UpdateRect(screen, 0, 0, 0, 0);
        
    /* Pause. */
    SDL_Delay(5000);
        
    return 0;
}
\end{verbatim}
\end{codelisting}

I would include a screen shot, but there's not much to see (it looks very much like a white line). If you don't understand the exact mechanics of the algorithm, don't worry. Bresenham is the sort of algorithm you can implement from canned code without too much trouble. Of course, it's always best to understand exactly what your code is doing, so I suggest picking up a book on graphics algorithms at your convenience if you're still puzzled.\footnote{Books about graphics algorithms are always an interesting read. In particular, I recommend the \othertitle{Graphics Gems} series and Alan Watt's \othertitle{3D Computer Graphics}.}

As a side note, the Bresenham algorithm can be applied to many types of interpolation problems, including texture mapping and bitmap scaling. There are several more optimized variants of the Bresenham algorithm than the simple one I've presented here.

What happens if a line does not lie entirely within the bounds of the screen? \code{DrawLine16} assumes that the line's coordinates are inside the screen. If you called \code{DrawLine16} with the coordinates $(-10, -20)$ and $(1000,3000)$, it would probably crash. Since the \gametitle{Penguin Warrior} display shows only a small part of a large game world, it's very likely that a given phaser beam will start or end outside of the currently displayed area.

We can handle this problem by \firstterm{clipping} phaser beams against the screen's rectangle. The \code{ClipLineAgainstRectangle} function takes a line segment and a rectangle as input and adjusts the segment's coordinates to encompass only its visible section. If the line segment is at least partially visible, \code{ClipLineAgainstRectangle} returns 1; otherwise it returns 0. We'll filter phaser beams through this function to make sure that we don't give \code{DrawLine16} invalid coordinates.

\begin{note}{Source Files}
The code from this section is available in the \filename{lines/} directory of the source archive. \filename{bresline.c} is a simple implementation of the Bresenham algorithm with SDL, and \filename{lineclip.c} contains the \code{ClipLineAgainstRectangle} routine. The latter is long and boring, so we haven't included its code in this chapter.
\end{note}

\index{lines|)}

\subsection{Detecting Phaser Hits}

Now we need a way to figure out whether a phaser shot met its mark. But what exactly is a ``hit'' and what is a ``miss''? A phaser hits its target if the beam passes through the target's bounding circle (a circle that encloses the object). For the purposes of \gametitle{Penguin Warrior}, this bounding circle will have a diameter equal to the width of the ship graphic.

\index{lines!distance from object}
How do we find the distance between the center of an object and a line? Look at Figure \ref{fig:phasers}. We'll treat the phaser beam as a vector $v_1$, and we'll call the vector that points from the source to the target $v_2$. We want to find the length of the shortest line segment from a point on $v_1$ to the target (the end of $v_2$). The shortest segment (which we'll call the distance from the phaser beam to the target) forms a 90-degree angle with $v_1$.

We can find the position of this segment along $v_1$ with a \firstterm{projection}. We'll leave the math behind this to other sources, but we can find this point with the following:

\begin{align*}
v_{1x} &= x_2 - x_1 \\
v_{1y} &= y_2 - y_1 \\
v_{2x} &= a - x_1 \\
v_{2y} &= b - y_1 \\
p_x &= \frac{(v_{1x} v_{2x} + v_{1y} v_{2y}) v_{1x}}{(v_{1x}v_{1x})^2 + (v_{1y}v_{1y})^2} \\
p_y &= \frac{(v_{1x} v_{2x} + v_{1y} v_{2y}) v_{1y}}{(v_{1x}v_{1x})^2 + (v_{1y}v_{1y})^2} \\
\end{align*}

This is essentially the normalized vector $v_1$ scaled by the dot product of $v_1$ and $v_2$. It works because the dot product of two vectors is proportional to the cosine of the angle between the vectors. Once we know the coordinate $(p_x,p_y)$, we can easily calculate the distance $d$ between the phaser beam and the target:

\begin{align*}
d &= \sqrt{(p_x - a)^2 + (p_y - b)^2} \\
\end{align*}

\extfigure{figures/phasers}{fig:phasers}{Testing phaser proximity}

The routine \code{CheckPhaserHit} in the source file \filename{weapon.c} implements this formula. With the math just shown, the code is straightforward.

\subsection{Imposing a Sane Rate of Fire}

As much fun as it would be to lean on the Fire button and project a line of death from the front of your ship, this wouldn't make for a very competitive game. To avoid this problem, we'll implement a phaser ``charging'' system. A player can fire only when the phasers have a certain level of charge, indicated by a small on-screen meter. Phasers take a few seconds to recharge fully, and they discharge quickly when they are fired.

Each player has a running charge count that is incremented by 30 each second. To accomplish this charging in a uniform way, the main loop adds the global \code{time\_scale} variable (see page \pageref{sect:pwtiming}) to the count at the start of each frame. Whenever a player fires, the charge count loses a certain amount. The player can employ a number of strategies to deal with the limited amount of charge.

With a bit more voodoo throughout \filename{main.c}, \filename{network.c}, and \filename{weapon.c}, \gametitle{Penguin Warrior} now has functional phasers. It's not finished yet, though---it still needs score and weapon charge counters. We'll add those next.

\section{Creating Status Displays}

At this point you can fly a ship around the \gametitle{Penguin Warrior} world and fire at a human or computer opponent. However, unless you keep a console visible behind the game's window, it's hard to tell what's going on in the game. \gametitle{Penguin Warrior} needs an on-screen status display.

But why use an ordinary font-based\footnote{Should you ever need to draw TrueType fonts in an SDL application, the SDL\_ttf library may be helpful. I won't discuss it further in this book, but it carries my recommendation as a well-written and easy-to-use library.} status display when we can do something a bit unusual? In the course of another project (a random caffeine-driven coding session, actually), I wrote a set of routines for simulating light-emitting diodes (LEDs) on top of SDL surfaces. The library takes any 8-bit SDL surface as input and draws a simulated board of LEDs that represents the image. In addition to image handling, the library also has limited support for drawing characters to simulated displays. The code is simple, but the effect is neat.

\extfigure{figures/leddemo}{fig:leddemo}{The LED simulator}

The LED library isn't really an SDL add-on package; it's just a few routines that happen to follow SDL's naming conventions. It's not substantial enough to release as an SDL extension library. We'll bring its routines directly into the \gametitle{Penguin Warrior} source code as \filename{status.c}.

% Author: Below, should "as long entry 0" be "as long as entry 0"? (add "as"?) Not clear.
LED displays are represented as 8-bit SDL surfaces. Each light in the display corresponds to one pixel of the surface. If the pixel has a color value of 0, the corresponding light is off; otherwise it is on. The surface's palette is ignored, since a light has only two possible states.\footnote{Actually, SDL does use the palette for blitting. The actual colors don't matter, as long as entry 0 (off-LED) is different from the others.} The use of 8-bit SDL surfaces is convenient because it lets us draw to simulated LED displays with SDL's normal \code{SDL\_BlitSurface} function. We can access an LED display's lights individually by locking the surface and editing its \code{pixels} member.

Listing \ref{code:pwstatus} uses the LED simulator to implement a status display system for \gametitle{Penguin Warrior}.

\begin{codelisting}{status.c}{code:pwstatus}
\begin{verbatim}
#include <SDL/SDL.h>
#include <stdio.h>
#include <stdlib.h>
#include "gamedefs.h"
#include "font5x5.h"   /* A simple 5x5 ASCII font, stored
                          as strings of X's.
                          The font gets compiled into
                          the program. */

/* =====================
   LED display routines.
   ===================== */

typedef struct LED_Display {
    SDL_Surface *led_surface;
    int phys_w, phys_h;
    int virt_w, virt_h;
    int virt_x, virt_y;
    SDL_Surface *on_image;
    SDL_Surface *off_image;
} LED_Display;

static int LED_CreateDisplay(LED_Display *disp, int cols, int rows,
                             int vcols, int vrows, char *on, char *off);
static void LED_FreeDisplay(LED_Display *disp);
static void LED_DrawDisplay(LED_Display *disp, SDL_Surface *dest,
                            int x, int y);
static void DrawChar5x5(SDL_Surface *dest, char ch, Uint8 color,
                        int x, int y);

/* Initializes an LED display. The parameters are as follows:
   cols, rows - Physical size of the LED display, in LEDs.
   vcols, vrows - Size of the LED display's framebuffer, in LEDs.
                  The visible area of the display can be scrolled.
   on, off - Filenames of the bitmaps to use for the "on" LED and
             the "off" LED.
   Returns 0 on success, -1 on error. */
static int LED_CreateDisplay(LED_Display *disp, int cols, int rows,
                             int vcols, int vrows, char *on, char *off)
{
    SDL_Color c;
    int i;

    memset(disp, 0, sizeof (LED_Display));

    disp->led_surface = SDL_CreateRGBSurface(SDL_SWSURFACE, vcols, vrows,
                                             8, 0, 0, 0, 0);
    if (disp->led_surface == NULL)
        return -1;

    disp->virt_w = vcols;
    disp->virt_h = vrows;
    disp->phys_w = cols;
    disp->phys_h = rows;
    disp->virt_x = 0;
    disp->virt_y = 0;

    for (i = 0; i < 256; i++) {
        c.r = i;
        c.g = i;
        c.b = i;
        SDL_SetColors(disp->led_surface, &c, i, 1);
    }

    SDL_LockSurface(disp->led_surface);
    memset(disp->led_surface->pixels, 0,
           disp->led_surface->pitch * vrows);

    SDL_UnlockSurface(disp->led_surface);

    disp->on_image = SDL_LoadBMP(on);
    if (disp->on_image == NULL) return -1;

    disp->off_image = SDL_LoadBMP(off);
    if (disp->off_image == NULL) return -1;

    /* Add alpha for a nice overlay effect. */
    SDL_SetAlpha(disp->off_image, SDL_SRCALPHA, 128);

    return 0;
}


/* Frees an LED display's memory. */
static void LED_FreeDisplay(LED_Display *disp)
{
    if (disp->led_surface != NULL)
        SDL_FreeSurface(disp->led_surface);

    if (disp->on_image != NULL)
        SDL_FreeSurface(disp->on_image);

    if (disp->off_image != NULL)
        SDL_FreeSurface(disp->off_image);

    memset(disp, 0, sizeof (LED_Display));
}


/* Renders an LED display to an SDL surface, starting
   at (x,y) on the destination. */
static void LED_DrawDisplay(LED_Display *disp,
                            SDL_Surface *dest, int x, int y)
{
    int row, col;
    SDL_Rect srcrect, destrect;
    Uint8 *leds;

    srcrect.w = disp->on_image->w;
    srcrect.h = disp->on_image->h;
    srcrect.x = 0;
    srcrect.y = 0;
    destrect = srcrect;

    SDL_LockSurface(disp->led_surface);
    leds = (Uint8 *)disp->led_surface->pixels;

    for (row = 0; row < disp->phys_h; row++) {
        for (col = 0; col < disp->phys_w; col++) {
            int led;

            destrect.x = col * disp->on_image->w + x;
            destrect.y = row * disp->on_image->h + y;
            led = leds[(row + disp->virt_y) *
                  disp->led_surface->pitch + col + disp->virt_x];
            if (led) {
                SDL_BlitSurface(disp->on_image, &srcrect,
                        dest, &destrect);
            } else {
                SDL_BlitSurface(disp->off_image, &srcrect,
                        dest, &destrect);
            }
        }
    }

    SDL_UnlockSurface(disp->led_surface);
}


/* Draws a 5x5 bitmapped character to the given 8-bit
   SDL surface. */
static void DrawChar5x5(SDL_Surface *dest, char ch,
                        Uint8 color, int x, int y)
{
    char *data;
    Uint8 *pixels;
    int sx, sy;

    data = Font5x5[(int)ch];

    if (SDL_MUSTLOCK(dest))
        SDL_LockSurface(dest);
    pixels = (Uint8 *)dest->pixels;

    for (sy = 0; sy < 5; sy++) {
        for (sx = 0; sx < 5; sx++) {
            if (data[5*sy+sx] != ' ') {
                pixels[dest->pitch*(y+sy)+x+sx] = color;
            } else {
                pixels[dest->pitch*(y+sy)+x+sx] = 0;
            }
        }
    }

    SDL_UnlockSurface(dest);
}


/* =====================================
   End of LED stuff.
   Back to the world of Penguin Warrior.
   ===================================== */

/* A temporary buffer for the characters currently on the display. */
#define SCROLLER_BUF_SIZE 10
char scroller_buf[SCROLLER_BUF_SIZE];

/* Message to scroll. This can be changed. */
const char *scroller_msg = "Welcome to Penguin Warrior";
int scroller_pos = 0;
int scroller_ticks = 0;

/* Various LED displays that appear on the Penguin Warrior screen. */
LED_Display player_score, player_shields, player_charge;
LED_Display opponent_score, opponent_shields;
LED_Display status_msg;


int InitStatusDisplay(void)
{
    if (LED_CreateDisplay(&player_score, 12, 5, 12, 5,
                  "led-red-on.bmp", "led-red-off.bmp") < 0)
        return -1;
    if (LED_CreateDisplay(&player_shields, 12, 1, 12, 1,
                  "led-red-on.bmp", "led-red-off.bmp") < 0)
        return -1;
    if (LED_CreateDisplay(&player_charge, 80, 1, 80, 1,
                  "led-red-on.bmp", "led-red-off.bmp") < 0)
        return -1;
    if (LED_CreateDisplay(&opponent_score, 12, 5, 12, 5,
                  "led-red-on.bmp", "led-red-off.bmp") < 0)
        return -1;
    if (LED_CreateDisplay(&opponent_shields, 12, 1, 12, 1,
                  "led-red-on.bmp", "led-red-off.bmp") < 0)
        return -1;
    if (LED_CreateDisplay(&status_msg, 56, 5, 66, 5,
                  "led-green-on.bmp", "led-green-off.bmp") < 0)
        return -1;

    memset(scroller_buf, 0, SCROLLER_BUF_SIZE);

    return 0;
}


void CleanupStatusDisplay(void)
{
    LED_FreeDisplay(&player_score);
    LED_FreeDisplay(&player_shields);
    LED_FreeDisplay(&player_charge);
    LED_FreeDisplay(&opponent_score);
    LED_FreeDisplay(&opponent_shields);
    LED_FreeDisplay(&status_msg);
}



void SetStatusMessage(const char *msg)
{
    scroller_pos = 0;
    scroller_msg = msg;
}


void SetPlayerStatusInfo(int score, int shields, int charge)
{
    char buf[3];
    Uint8 *pixels;
    int i;

    /* Set the score counter. */
    sprintf(buf, "%2i", score);
    DrawChar5x5(player_score.led_surface, buf[0], 1, 0, 0);
    DrawChar5x5(player_score.led_surface, buf[1], 1, 6, 0);

    /* Set the shield bar. */
    SDL_LockSurface(player_shields.led_surface);
    pixels = (Uint8 *)player_shields.led_surface->pixels;
    for (i = 0; i < 12; i++) {
        if (i < shields * 12 / 100)
            pixels[i] = 1;
        else
            pixels[i] = 0;
    }
    SDL_UnlockSurface(player_shields.led_surface);

    /* Set the phaser charge bar. */
    SDL_LockSurface(player_charge.led_surface);
    pixels = (Uint8 *)player_charge.led_surface->pixels;
    for (i = 0; i < 80; i++) {
        if (i < charge * 80 / PHASER_CHARGE_MAX)
            pixels[i] = 1;
        else
            pixels[i] = 0;
    }
    SDL_UnlockSurface(player_charge.led_surface);
}


void SetOpponentStatusInfo(int score, int shields)
{
    char buf[3];
    Uint8 *pixels;
    int i;

    /* Set the score counter. */
    sprintf(buf, "%2i", score);
    DrawChar5x5(opponent_score.led_surface, buf[0], 1, 0, 0);
    DrawChar5x5(opponent_score.led_surface, buf[1], 1, 6, 0);

    /* Set the shield bar. */
    SDL_LockSurface(opponent_shields.led_surface);
    pixels = (Uint8 *)opponent_shields.led_surface->pixels;
    for (i = 0; i < 12; i++) {
        if (i < shields * 12 / 100)
            pixels[i] = 1;
        else
            pixels[i] = 0;
    }
    SDL_UnlockSurface(opponent_shields.led_surface);
}


void UpdateStatusDisplay(SDL_Surface *screen)
{
    int i;

    /* Update the scroller.
       This is not linked to the global time_scale, since speed
       really doesn't matter. The only effect of a high framerate
       would be that the scrolling message would move faster. */
    if ((scroller_ticks % 6) == 0) {
        char ch;
        for (i = 0; i < SCROLLER_BUF_SIZE-1; i++) {
            scroller_buf[i] = scroller_buf[i+1];
        }
        if (scroller_msg[scroller_pos] == '\0') {
            ch = ' ';
            scroller_pos--;
        } else {
            ch = scroller_msg[scroller_pos];
        }
        scroller_pos++;
        scroller_buf[i] = ch;
        status_msg.virt_x = 0;
        for (i = 0; i < SCROLLER_BUF_SIZE; i++) {
            DrawChar5x5(status_msg.led_surface, scroller_buf[i],
                        1, 6 * i, 0);
        }
    } else {
        status_msg.virt_x++;
    }

    scroller_ticks++;

    LED_DrawDisplay(&player_score, screen, 0, 0);
    LED_DrawDisplay(&player_shields, screen, 0, 48);
    LED_DrawDisplay(&player_charge, screen, 0, 471);
    LED_DrawDisplay(&opponent_score, screen, 544, 0);
    LED_DrawDisplay(&opponent_shields, screen, 544, 48);
    LED_DrawDisplay(&status_msg, screen, 96, 0);
}
\end{verbatim}
\end{codelisting}

\filename{status.c} is divided into two parts. The first part implements the LED simulator described earlier, and the second part uses the simulator to create game status displays for \gametitle{Penguin Warrior}. \gametitle{Penguin Warrior's} status display consists of six pieces: a scoreboard and shield block for each player, a scrolling message banner, and a phaser charge readout for the local player. Each piece has its own \code{LED\_Display} structure.

To use the status display system, the game engine calls \code{InitStatusDisplay} at startup and then executes \code{UpdateStatusDisplay} during each frame. Whenever a game statistic (score, shield level, or phaser charge) changes, the game calls \code{SetPlayerStatusInfo} or \code{SetOpponentStatusInfo} to update the information. It can set new banner messages with \code{SetStatusMessage}. The LED simulator redraws the boards at each frame. Although alpha-blended blits are relatively slow, this update is negligible in comparison to the rest of the game loop. Figure \ref{fig:pwstatus} shows the new status display system.

\extfigure{figures/pwstatus}{fig:pwstatus}{\gametitle{Penguin Warrior}'s status display}

\gametitle{Penguin Warrior} is complete, at long last! It's a relatively small game, but it contains all of the major ingredients you'd find in a larger production, and the engine performs reasonably well. The next section discusses some of the things I could have done differently throughout the project.

\section{In Retrospect}

I created \gametitle{Penguin Warrior} to demonstrate game programming in the Linux environment. I started writing it while I was trying to decide where to go with Chapter \ref{ch:masteringsdl}, and I've added features throughout the book to demonstrate various topics that came up. There was no formal design process, other than the planning that went into the book as a whole. \gametitle{Penguin Warrior} turned out well enough, but it's an incredibly simple game compared to most. At the risk of hypocrisy, I strongly recommend that you put considerable thought into the design of your games. It's easy to write games that look good; it's much harder to write games that play well. Admittedly, \gametitle{Penguin Warrior} doesn't have much depth.

What could I have done differently? Here are a few things that come to mind.

\begin{itemize}
\item I could have used \cplusplus{} instead of C. \cplusplus{} lends itself well to game programming, since games usually simulate interactions between physical objects to some extent. In fact, any object-oriented programming language would be beneficial in that regard. There are plenty of arguments for and against \cplusplus{}, and it's the topic of many mailing list holy wars.

\item \gametitle{Penguin Warrior} would benefit from a more sophisticated networking system. The current TCP-based system, in which the clients take turns exchanging packets, is simple, but its performance is highly dependent upon the quality of the underlying network. A UDP-based protocol would be more complex to implement, but it could potentially offer better performance.

\item In hindsight, Tcl was not an especially good choice for the scripting engine. It works in this case, but it's not a very good solution for number crunching or managing large amounts of data. If I were to rewrite Chapter \ref{ch:gamescripting}, I would probably choose a Lisp variant such as Scheme.

\item Resource management is always a challenge. The present implementation of \gametitle{Penguin Warrior} uses an ad hoc resource-loading routine, but this task could be performed in a more general and flexible way. Each resource (a sound clip or a graphic) could have a symbolic name, and the resource manager could load them on demand.

\item The source tree would be much easier to build if it had a GNU Autoconf script. As it stands, you usually need to tweak the makefile to build the game. While this is fine for developers, it might be confusing for end users.
\end{itemize}

There are also some gameplay features that would make nice additions to \gametitle{Penguin Warrior} (and perhaps good weekend projects for interested readers):

\begin{itemize}
\item More weapons. Many games allow the player to pick up ``power-ups'' from the playing field. Perhaps \gametitle{Penguin Warrior} could have floating Power Penguins for the players to find.
\item A radar screen for tracking the opponent. Alpha blending could add a nice effect to such a display.
\item The ability to have two scripted opponents play each other over the network. This would be useless but fun to watch.
\end{itemize}

That's it for \gametitle{Penguin Warrior}. The next chapter talks about the various Linux distributions and package managers you have to contend with, as well as FreeBSD portability. It also discusses the Loki Setup tool, a program for installing games in a distribution-independent way.
