The Body Electric

Those pink checkerboards just had to go.  Representing 
different building types by colored patterns is not my idea 
of an intuitive user interface, so this month I introduce 
bitmaps to enhance the combat map.

To facilitate bitmap programming, a new BITMAP module has 
been added.  All bitmaps are loaded from the executable's 
resources and stored in a memory presentation space.  From 
there, they are displayed with a GpiBitBlt() call.

The game now allows you to move a 'Mech across the playing 
field.  The 'Mech starts out at location (0,0) and can be 
moved one hex at a time.  Simply click on a nearby hexagon, 
and the 'Mech will either turn to face it or move onto it.  
In BattleTech, each sixty-degree turn is one movement point, 
and a 'Mech can only move forward or backward.  The current 
version of this game does not keep track of movement points 
or turns, however.

During targetting, the source hexagon is always taken to be 
the 'Mech, so once you click with the left mouse button, the 
targetting line is automatically drawn to the mouse pointer.

BITMAP
------

This new module provides generic bitmap support for the 
entire game.  All bitmaps are stored in the same memory 
presentation space.

BitmapLoad() loads a bitmap and returns a bitmap handle if it 
succeeds.  It also initializes the module when first called.  
Note all the WinMessageBox() calls to indicate an error.  A 
more advanced error handler will be included in a future 
version.

BitmapDraw() draws a bitmap on the client window.  Most 
bitmaps in this game are irregular in shape.  The bitmap 
itself is always rectangular, but the image it holds usually 
is not.  For instance, a bitmap of a terrain is hexagonal.  
The four corners are part of the bitmap, but not part of the 
image.

Thus there is a problem.  Some bitmap pixels must be drawn 
over the screen, and others should not overwrite anything.  
In other words, only a portion of the rectangular bitmap 
should be drawn.  The rest should be ignored.

By using a bitmap mask, this effect can be achieved.  The 
mask is the same size and shape as the original bitmap and is 
drawn at the same location, but it contains only two colors: 
white and black.  The black portions are where the true image 
exists in original bitmap.  The white areas are where the 
background image should show through.  It is important to 
remember that wherever the mask is white, the original bitmap 
should be black.

The mask is logically-AND'ed with the screen.  The black 
portions of the mask erase the screen, and the white portions 
leave the screen unaltered.  Then the original bitmap is 
logically-OR'ed onto the screen over the mask.

If the mask bitmap handle is NULLHANDLE, then no masking 
operation is performed.  This is handy for times when the 
background is already known to be black.

GAME
----

Most of the window-related variables have been moved to a new 
module, WINDOW.  However, all of the code is tentatively 
still here.  See the discussion of WINDOW for details. 

The window procedure has been updated only to reflect changes 
in the other modules.  For instance, WM_CREATE now calls 
MechInit() to initialize the MECH module.

HEXES
-----         

The biggest change in this module is the technique used to 
draw the combat map.  The addition of bitmap support 
necessitated an update of the drawing functions.  Also, the 
routines which support the different terrains have been moved 
to a separate module, TERRAIN.

The following two methods were previously used to draw the 
combat map:

1. The client window is painted black.  For each hexagon, the 
current color and pattern is set, and an area bracket is 
started. A hexagon is drawn, the bracket is closed, and OS/2 
fills in the rest.

2. Similar to the first method, except that the client window 
is painted with the color and pattern of the default terrain 
(in this case, clear ground).  A black hexagonal grid is 
overlaid.  Only the hexagons which differ from the default 
are then painted.

Beginning this month, a third technique comes into play:

3.  The client window is once again painted black, the color 
of the hexagonal grid.  The inside of each hexagon is painted 
without redrawing the hexagon itself.

The window procedure should take no longer than 1/10th of a 
second to process any message, otherwise it blocks the 
message queue for too long.  Unfortunately, drawing the 
entire combat map can take several seconds.

One solution is to move HexDrawMap() into a separate thread. 
This may solve the queue problem, but the combat map still 
takes too long to draw.  Method #2 works well as long as the 
combat field is mostly of clear ground, which is not always 
the case.

The proper technique would be to use WinQueryWindowRect() to 
determine the hexagons which need to be redrawn.  This 
function returns a rectangle that outlines the region of the 
client window which has been invalidated.  A function similar 
to HexLocate() could be used to find the four hexagons that 
lie on the corners of that rectangle.  Then HexDrawMap() 
would read:

   for (hi.c=iStartC; hi.c<=iEndC; hi.c++)
     for (hi.r=iStartR; hi.r<=iEndR; hi.r++)
       HexFillDraw(hi);

The individual hexagons, however, still take a long time to 
draw. There are a few potential solutions to this problem.  
Look for these and other speed enhancements in future 
articles:

1. Draw all hexagons of a given terrain at once, thereby 
avoiding multiple calls to GpiSetColor() and GpiSetPattern().

2. Use a different technique for drawing filled polygons.  I 
have not explored all the possibilities yet, so there may be 
other Gpi functions which work better.

3. Use bitmaps for all the terrains.  The GpiBitBlt() 
function might be faster than area-bracket fills.

Two new defines, XLAG and YLAG, represent the horizontal and 
vertical spacing between the hexagons on the hex map.  The 
default value of two produces a spacing of one pixel.  These 
defines only affect HexCoord(), HexMidpoint(), and 
HexLocate(), since these are the only functions which 
maintain a link between screen coordinates and hex indices.  
WINDOW_WIDTH and WINDOW_HEIGHT also use XLAG and YLAG to 
determine the size of the client window needed to display the 
entire hex map.

MECH
---- 

This month introduces a user-controlled 'Mech, supported by 
the MECH module.  This module initializes the 'Mech's 
position and orientation in MechInit() and changes them in 
MechMove(). MechInit() also loads the bitmaps representing 
the 'Mech in all six orientations.

MechMove() has to determine whether the 'Mech needs to be 
rotated or moved.  First, it rejects any target hexagon which 
is not adjacent to the 'Mech.  Then, it calculates the 
direction to that hexagon.  If this value is different from 
the current orientation, the 'Mech is rotated and redrawn.  
Otherwise, it is moved to the new position.

If you wish to move the 'Mech to a hexagon on its left or 
right side, click on the target hex once to rotate the 'Mech 
and again to move him.  However, do not click the mouse twice 
too quickly.  This action is registered as a double-click, 
which means that OS/2 does not send two WM_BUTTON1DOWN 
messages.  Rather, it sends one WM_BUTTON1DOWN followed by a 
WM_BUTTON1DBLCLK.

MENU
----

Changes in the header file for this module eliminate the need 
for MenuInit(), since this function did nothing but 
initialize global variables.  A separate if-statment in 
MainCommand() allows this function to handle any number of 
terrains without a code change. The only other change to this 
module is the inclusion of my address in the "About..." 
dialog box.

TERRAIN
-------

This module encapsulates all the terrain-related code and 
data which was once in HEXES.  There are eleven terrain 
types, and each one has a set of attributes listed in 
structure TERRAIN. Function TerrainInit() initializes the 
array of terrain data, ater[], with pre-defined constants.  A 
configuration file will eventually be created to store 
information on all the terrains.

Function TerrainIdFromMenu() returns the terrain ID (a number 
from 0 to 10) which maps to a given IDM_TER_xxx value.  In 
previous versions of this game, the terrain ID was the same 
as the menu ID listed in file RESOURCE.H.

WINDOW
------

This module does not contain any code yet, but its purpose is 
to localize most of the presentation-manager specific 
features of this game.  In the distant future, there might be 
interest in porting this game to other platforms.  Separating 
the operating system specific code early on will make 
cross-platform development much easier.

Next Month
----------

There are a few quirks and minor bugs in this code which have 
yet to be fixed.  The mouse is never captured during 
targetting.  Clicking on the client window will not bring it 
into focus.  The info box is not hidden when the main window 
is minimized.  These and other problems will be addressed in 
the next installment.

I would also like to make a formal request for volunteer 
translators.  As you know, multilingual support is one of the 
goals of this project.  I expect to have a few dozen 
single-line strings (most of them error messages) which need 
to be translated into as many foriegn languages as possible.  
Anyone out there interested in lending a hand?