Paint by Numbers This month, we will make our first attempt at creating a real terrain for the combat map. Each hexagon is a thirty-meter wide area of a particular "terrain", such as grasslands, water, buildings, and so on. There is still a lot of work left, however. The terrain is represented by colors and patterns instead of bitmaps. So instead of seeing an icon of a building inside a hexagon, you see a shade of pink. Yes, I realize that pink is a dumb color for a building. Like I said, the program needs work, and bitmaps will have to wait until I decide how scrolling and scaling will be handled. Ideally, the map could have any size (even hundreds of hexagons per side) that can be scrolled in the four main directions and scaled to any size. The only problem with this grandiose idea is the speed at which it runs. The current version has a fixed window without bitmaps, and drawing the entire map takes several seconds. Most of the time is spent filling the hexagons, a task handled by function HexFillDraw(), shown in Listing 1. Filling an area takes too long, so I need to find a faster method. Any suggestions? The pull-down menus allow you to toggle edit-mode and select the terrain type, and a simple mouse click will change a hexagon to the new terrain. Unfortunately, the load and save options are not available, so you will have to wait until next month before you can share your playing fields with the rest of the world. But since my articles are due about two months before they are published, you can cheat a little by skipping ahead to the code for the October issue. When I submit an article to "OS/2 Monthly", I also upload the accompanying source code to my local BBS. So the code for the October issue is actually available in early August. The overall structure of the program has been slightly modified. For starters, global variables are declared differently. True object-oriented techniques frown upon global variables, but sometimes they can result in simpler and faster code. Listing 1 is an excerpt from HEXES.C that implements the idea. This technique may or may not be original, but I haven't seen it before. The constant HEXES_C is defined at the top of HEXES.C, and is used to alter the declaration of these globals depending on whether they are being included inside HEXES.C. The compiler sees the line "HPS hpsHex" when it processes HEXES.C, but it sees "extern HPS hpsHex" when it compiles any other file. Therefore, the declaration and definition of this variable are in the same place. The other unorthodox change is also related to global variables. The presentation space handles are now created once during initialization and kept open until the program terminates. This has three advantages. First, stack space is reduced because these variables are no longer local to the window procedure. Second, speed is increased because the handles are only obtained once, and not every time a button is pressed. Third, function calls are simplified since the handles are stored in global variables and not passed as parameters. I haven't seen this technique used elsewhere either, which means it will probably backfire on me six months down the road. There may be a problem with keeping a P.S. handle open across successive window procedure calls. However, it appears to work flawlessly, and it does produce more efficient code. The targetting routines have been moved from the window procedure to their own module. The primary reason for this change was to reduce the size of WinProc(). With the addition of pull-down menus, the mouse has gained a second function - map editing. The routines for changing and drawing the map are in the same module, but this combination may be split in the future, especially since the load and save features will be added next month. These features require dialog boxes, which will be the primary focus of the September installment. Each hexagon on the map has various attributes associated with it. These attributes are stored in a two-dimensional array of structures called amap. The structure, aptly labelled MAP, only stores the terrain type and the height. More fields will be added as the mapping system becomes more complex. The terrain types are listed in the resource file and cover the majority of features one would find on a typical combat field. Of course, there is plenty of room for additions if anyone would like to submit their ideas. Pull-down menus are being used because it is the easiest way to incorporate these features into the program. The menu for terrain selection will eventually be replaced by something more graphical. One possibility would be to create a second window that shows each of the terrain types as a small icon on which you can select. Clicking with the left button will make that terrain the current choice. Clicking with the right button will pop up an information window showing the attributes of that terrain, such as how it affects visibility and maneuverability. This game deviates from the rule books in its treatment of height. In the original game, hills were simply another type of terrain. I want a little more flexibility than that, because there is no reason why woods or buildings cannot be located on elevated ground. Representing the change in elevation is tricky. Using different shades or intensities would render the map illegible because of the confusing array of colors. Surrounding all the hexagons of equal height with a contour line requires a very complex and time-consuming algorithm. Showing a number inside a hexagon is simple, but it might not be distinguishable from any bitmap which would also occupy the hex. As you can see, support for height was not been included this month since I have not yet decided on how to represent it on the screen. The variable eMode (an enumerated type defined in GAME.C) indicates the user's current activity. This version supports only two choices: targetting and editing. Targetting is the initial mode and the code is identical to last month's. The source hexagon cycles more quickly during targetting, but nothing else has changed. The next addition to targetting will be a display of the current angle and length of the targetting line. As you move the line, the computer will tell you at what bearing and range your target is. It will also calculate the visibility and inform you of the chance of hitting the target. In the final version of this game, the map editor will be separate from the combat phase. Changing the playing field during a battle is not a priviledge that mere mortals can enjoy. Once a campaign has been created, it cannot be changed during play. However, the editor will remain connected to the rest of the game until it is completed. The window procedure has changed the most. WM_CREATE perfoms more initializations, especially since the presentation space handles are now global variables. Variable bDefTerrain is used by WM_PAINT to draw the window background quickly. Any hexagons which are the same color as the background are not redrawn. The default is the color for clear ground, which will most likely be the majority of the map. The button-down routine (WM_BUTTON1DOWN message) checks the current mode, and either activates targetting or changes the hexagon's terrain type. This is the only message that is important to both modes. WM_BUTTON1UP and WM_MOUSEMOVE are important only to targetting, so these routines have not changed significantly. The WM_COMMAND message processes all the menu selections. If any of the terrain types have been selected, than the check-mark for the current terrain is cleared, and the new terrain is selected. When the user clicks on "Edit," the the user mode is toggled. That wraps it up for this month. I hope that obtaining the source code has not been a problem, but we are still working on opening the distribution channels. The main focus in the September issue will be dialog boxes and lots of other good stuff. Listing 1: functions HexDraw() and HexFillDraw() --------- void HexDraw(HPS hps, HEXINDEX hi) { POINTL ptlHex[]={ {HEX_SIDE,0}, {HEX_SIDE+HEX_EXT,HEX_HEIGHT/2}, {HEX_SIDE,HEX_HEIGHT}, {0,HEX_HEIGHT}, {-HEX_EXT,HEX_HEIGHT/2}, {0,0} }; int i=0; ptlHex[5]=HexCoord(hi); GpiMove(hps,&ptlHex[5]); for (;i<5;i++) { ptlHex[i].x+=ptlHex[5].x; ptlHex[i].y+=ptlHex[5].y; } GpiPolyLine(hps,6L,&ptlHex[0]); } void HexFillDraw(HEXINDEX hi) { GpiSetColor(hpsHex,HexTerrainColor(abMap[hi.c][hi.r].bTerrain)); GpiSetPattern(hpsHex,HexTerrainPattern(abMap[hi.c][hi.r].bTerrain)); GpiBeginArea(hpsHex,BA_NOBOUNDARY); HexDraw(hpsHex,hi); GpiEndArea(hpsHex); GpiSetColor(hpsHex,HEX_COLOR); HexDraw(hpsHex,hi); } Listing 2: "#define EXTERN extern" trick --------- #ifdef HEXES_C #define EXTERN #else #define EXTERN extern #endif EXTERN HPS hpsHex; EXTERN TARGET target; EXTERN long lNumColors; EXTERN MAP amap[NUM_COLUMNS][NUM_ROWS]; #undef EXTERN