Some global variables and notes. Bolo maps are 256x256 (0-255), but the outside 20 squares are designated as a border into which nothing but start squares should go. In BoloMapEditor (BME) coordinates are 0-216, and are offset by 20 at save and read time. short width, height, xx, yy, dwidth, dheight; short size, pw, ph; width = map limit in horizontal direction (0-width) dwidth = display width xx = leftmost box displayed pw = width of window in pixels height, dheight, yy, ph = same for vertical size = pixels / box (4, 8, or 16) While I will try to define all needed globals, I may miss some. Also, I have removed references to externs, which are globals defined in source files other than the one from which the routine is taken. Most of these should be fairly obvious. If not, write. The following routines allow access to the map data Byte *data, *data2; data = (Byte *) NewPtr(46700); data2 = data + 21600; short get_box(short x,short y) { short k; if ((x<0) || (y<0) || (x>215) || (y>215)) return(0xFF); asm { clr.l d0 move y,d0 mulu #216,d0 add x,d0 move.l data,a0 adda.l d0,a0 clr.l d1 move.b (a0),d1 move d1,k } return(k); } void set_box(short x,short y,short type) { Byte *localdata, old; if ((x<0) || (y<0) || (x>215) || (y>215)) return; old = get_box(x,y); if (y<100) { localdata = data + y * 216 + x; } else { localdata = data2 + (y-100) * 216 + x; } if (type == 254) { if (((old & 0xf7) > 1) && ((old & 0xf7) < 8)) { *localdata = old ^ 8; } } else { if (old == 23) nbases--; if (old == 24) npills--; if (type == 23) { if (nbases < 16) nbases++; else { set_box(x,y,old); return; } } if (type == 24) { if (npills < 16) npills++; else { set_box(x,y,old); return; } } *localdata = type; } } 0-15 = standard Bolo types 23 = base 24 = pillbox 255 = deepsea The crazy conniptions in set_box are because ThinkC can't access an array of bytes beyond (pointer+32k), and the speed gains in get_box from rewritting in assembly were fairly small. The original BME graphics were monochrome patterns, designed primarily to be viewed at a scale of 8 pixels square, or half of actual size. A Rect was set, a pattern (from a pat#) chosen, and the Rect painted. myPattern = GetResource('PAT#',MYPAT); HLock(myPattern); void draw_by_boxes() { short i, j; SetPort(windowPtr); SetCursor(watch); PenNormal(); DrawGrowIcon(windowPtr); DrawControls(windowPtr); for (j=yy;j<(yy+dheight);j++) { for (i=xx;i<(xx+dwidth);i++) { draw_box(i,j,1); } } PenNormal(); SetCursor(&arrow); set_mode(-1); } /* ---------------------------------------------------- data description 8 4 7 16 0 building 1 20 [2] [6] 1 water 2 2 2 2 2 swamp 3 3 3 3 3 crater 4 4 4 4 4 road 5 5 5 5 5 forest 6 6 6 6 6 rubble 7 7 7 7 7 grass 8 8 8 8 8 rocks 9 9 9 9 9 boat 10 2 2 [10] 10-15 2-7 w/mine 11-16 3-8 3-8 w/point drawing 23 base 23 21 [3] [7] 24 pillbox 24 22 [4] [8] FF deep_sea 16 16 16 16 [n] = PICT 200+n = pics[n] ---------------------------------------------------- */ void draw_box(short x,short y,short include_offset) { short k, xo, yo; Rect r; Pattern pat; GrafPtr currentptr; k = get_box(x,y); GetPort(¤tptr); if (include_offset != 0) { xo = xx; yo = yy; } else { xo = yo = 0; } SetRect(&r,size*(x-xo),size*(y-yo),size*(x-xo+1),size*(y-yo+1)); if(RectInRgn(&r,currentptr->visRgn) || printing == TRUE) { switch (size) { case 4: if (k==0) k = 20; if (k==23) k = 21; if (k==24) k = 22; if (k==255) k = 16; if ((k>8) && (k<16)) k -= 8; GetIndPattern(&pat,MYPAT,k+1); FillRect(&r,pat); break; case 8: if (k==255) k = 16; GetIndPattern(&pat,MYPAT,k+1); FillRect(&r,pat); break; case 16: if (k==255) k = 16; if (k==0) DrawPicture(pics[1],&r); else if (k==9) DrawPicture(pics[4],&r); else if (k==23) DrawPicture(pics[2],&r); else if (k==24) DrawPicture(pics[3],&r); else { if ((k>9) && (k<16)) { k -= 8; GetIndPattern(&pat,MYPAT,k+1); FillRect(&r,pat); SetRect(&r,size*(x-xo)+6,size*(y-yo)+6,size*(x-xo)+11,size*(y-yo)+11); PenPat(white); PaintRect(&r); MoveTo(size*(x-xo)+5,size*(y-yo)+5); Line(6,6); Move(-6,0); Line(6,-6); PenPat(black); Move(-3,1); Line(0,0); Move(2,2); Line(0,0); Move(-2,2); Line(0,0); Move(-2,-2); Line(0,0); } else { GetIndPattern(&pat,MYPAT,k+1); PenPat(pat); FillRect(&r,pat); } } break; } } } Note that more patterns had to be added for size=4, and PICT's had to be used for size=16. The mine-drawing for size=16 was particularly poor. Note also that the info in the table may not be entirely reliable, and that size=7 is no longer supported (an original size map would just print on a sheet of paper). In an effort to gain more speed, I went to offscreen bitmaps. This allowed very fast updates. However, it took some time to draw the bitmaps. By making use of the known character of the offscreen bitmaps (1 bit/pixel, bit-alignment, etc) I was able to write some assembly routines to prepare these bitmaps while avoiding QuickDraw. void draw_to_offscreen() { GrafPtr saveptr; SetCursor(watch); GetPort(&saveptr); SetPort(ofsptr); switch (size) { case 4: draw_off_4(); break; case 8: draw_off_8(); break; case 16: draw_off_16(); break; } SetPort(saveptr); SetCursor(&arrow); } void draw_off_4() { short x, y, k1, k2, rb; rb = (ofsptr->portBits).rowBytes; for (y=0;y8) && (k1<16)) k1 -= 8; k2 = get_box(x+1,y); if (k2==0) k2 = 20; if (k2==23) k2 = 21; if (k2==24) k2 = 22; if (k2==255) k2 = 16; if ((k2>8) && (k2<16)) k2 -= 8; asm { movem a2/d3,-(sp) move.l myPattern,a0 move.l (a0),a1 ; pointer to pat# move.l a1,a2 clr.l d0 move.w k1,d0 mulu #8,d0 add #2,d0 adda.l d0,a1 ; pointer to correct pattern 1 clr.l d0 move.w k2,d0 mulu #8,d0 add #2,d0 adda.l d0,a2 ; pointer to correct pattern 2 clr.l d1 clr.l d2 move.w rb,d2 move.l d2,d0 mulu #4,d0 move y,d1 mulu d1,d0 and.b #1,d1 ; if y odd beq @1 adda.l #4,a1 ; adjust pattern pointers adda.l #4,a2 @1 move.w x,d1 asr.w #1,d1 add.l d1,d0 move.l ofsbitmap,a0 adda.l d0,a0 ; pointer to bitmap move.l #3,d3 @2 move.b (a1)+,d0 ; copy and.b #240,d0 move.b (a2)+,d1 and.b #15,d1 add.b d1,d0 move.b d0,(a0) adda.l d2,a0 dbra d3,@2 movem (sp)+,a2/d3 } } // x } // y } void draw_off_8() { short x, y, k, rb; rb = (ofsptr->portBits).rowBytes; for (y=0;yportBits).rowBytes; for (y=0;y0) && (k<=16) && (k!=9)) { asm { movem a2/d3,-(sp) move.l myPattern,a0 move.l (a0),a1 ; pointer to pat# clr.l d0 move.w k,d0 cmp.b #8,d0 ; if k==8 don't remove mine beq @1 and #23,d0 @1 mulu #8,d0 add #2,d0 adda.l d0,a1 ; pointer to correct pattern clr.l d2 move.w rb,d2 move.l d2,d0 mulu #16,d0 mulu y,d0 clr.l d1 move.w x,d1 mulu #2,d1 add.l d1,d0 move.l ofsbitmap,a0 adda.l d0,a0 ; pointer to bitmap move.l a0,a2 sub #1,d2 move.l #7,d3 @2 move.b (a1),(a0)+ ; copy move.b (a1)+,(a0) adda.l d2,a0 dbra d3,@2 suba #8,a1 ; restore pattern move.l #7,d3 @3 move.b (a1),(a0)+ ; copy again move.b (a1)+,(a0) adda.l d2,a0 dbra d3,@3 clr.l d0 ; test for mine move.w k,d0 and.b #15,d0 cmp #8,d0 ble @4 move.l d2,d1 ; draw mine addq.l #1,d1 mulu #5,d1 adda.l d1,a2 and.b #251,(a2)+ ; one and.b #239,(a2) adda.l d2,a2 and.b #252,(a2)+ ; two and.b #159,(a2) adda.l d2,a2 and.b #252,(a2)+ ; three and.b #31,(a2) adda.l d2,a2 and.b #254,(a2)+ ; four and.b #191,(a2) adda.l d2,a2 and.b #252,(a2)+ ; five and.b #31,(a2) adda.l d2,a2 and.b #252,(a2)+ ; six and.b #159,(a2) adda.l d2,a2 and.b #251,(a2)+ ; seven and.b #239,(a2) @4 movem (sp)+,a2/d3 } // asm } else { SetRect(&r,size*(x),size*(y),size*(x+1),size*(y+1)); switch (k) { case 0: DrawPicture(pics[1],&r); break; case 9: DrawPicture(pics[4],&r); break; case 23: DrawPicture(pics[2],&r); break; case 24: DrawPicture(pics[3],&r); break; } } } // x } // y } void draw_from_offscreen() { Rect sr, dr; short i; SetPort(windowPtr); PenNormal(); DrawGrowIcon(windowPtr); DrawControls(windowPtr); SetRect(&sr,xx*size,yy*size,xx*size+pw-15,yy*size+ph-15); SetRect(&dr,0,0,pw-15,ph-15); CopyBits(&(ofsptr->portBits),&(windowPtr->portBits), &sr,&dr,srcCopy,NULL); set_mode(-1); } A personal note: I was rather proud of myself for these routines, since I had done virtually no assembly programming, but I got them all working one weekend and they are about 40 times faster than quickdraw for this specialized case. There is a global variable gmode which determines which graphics routines to use (0=by boxes, 1=offscreen bitmaps). Whenever the configuration of the offscreen bitmap must change (zoom in/out, set map size), the graphics mode is determined and configured. The following routine also has hooks for the Bolo-like graphics (gmode=2), which I'll come to later. GrafPort ofsport; GrafPtr ofsptr; Ptr ofsbitmap = NULL; short gmode, docolor8 = TRUE, docolor4 = TRUE; // gmode: 0=drawB&W, 1=offscreenB&W, 2=stuart void set_gmode() { short w; long bytesneeded, test; Rect r; Byte keymap[16]; if (ofsbitmap != NULL) { if (GetPtrSize(ofsbitmap) != 0) { DisposPtr(ofsbitmap); ofsbitmap = NULL; } } if (width < 20) width = 20; if (width > 216) width = 216; if (height < 20) height = 20; if (height > 216) height = 216; size_window(pw+1,ph+1); w = width; if ((w & 1) & (size == 4)) w += 1; gmode = 1; if (size==16) gmode = 2; if (size==8 && hasColorQD && (pixel_depth >= 4) && docolor8) gmode = 2; if (size==4 && hasColorQD && (pixel_depth >= 4) && docolor4) gmode = 2; GetKeys((void *) keymap); if (keymap[7] & 4) gmode = 2; if (gmode == 1) { bytesneeded = (size*size/8); bytesneeded = ((long) w) * height * bytesneeded; if (CompactMem(1507000) < (bytesneeded + 16384)) gmode = 0; } switch (gmode) { case 0: break; case 1: SetRect(&r,0,0,w*size,height*size); ofsptr = &ofsport; OpenPort(ofsptr); ofsbitmap = NewPtr(bytesneeded); if (ofsbitmap != NULL) { ((ofsptr->portBits)).baseAddr = ofsbitmap; ((ofsptr->portBits)).rowBytes = (w * size)/8; ((ofsptr->portBits)).bounds = r; RectRgn(ofsptr->visRgn,&r); RectRgn(ofsptr->clipRgn,&r); EraseRect(&(ofsptr->portRect)); } else { gmode = 0; } break; case 2: check_drawing_environment(); break; } if ((gmode == 1) && (ofsbitmap == NULL)) gmode = 0; if (gmode == 1) draw_to_offscreen(); EnableItem(optionMenu,1); EnableItem(optionMenu,2); if (size == 16) DisableItem(optionMenu,1); if (size == 4) DisableItem(optionMenu,2); } Rather than redrawing the offscreen bitmap each time a box is edited, the new terrain is drawn to both the screen and the offscreen area. void draw_boxes(short x,short y) { GrafPtr currentptr; if (x>=0 && y>=0 && x=xx && y>=yy && x #include #include #include typedef unsigned short Word; typedef unsigned long DWord; typedef union { DWord dword[2]; Word word[4]; Byte byte[ 8]; } QWord; typedef union { DWord dword[4]; Word word[8]; Byte byte[16]; } OCTWord; typedef union { Byte colour[4]; DWord d; } CTAB; typedef Byte MAP_X, MAP_Y; typedef Byte VIEW_X, VIEW_Y; typedef Word WORLD_X, WORLD_Y; typedef Byte PIX_X, PIX_Y; typedef Byte CELLNUM; typedef Word CHARCODE; typedef Byte CHARID; #define until(A) while (!(A)) #define elementsof(A) (sizeof(A)/sizeof((A)[0])) #define is_wet(A) ((A) == RIVER || (A) == BOAT || (A) == DEEPSEA) #define BBC_MAP_CHARS 0xD8 #define BACKGROUND_CHARS 0x100 #define PBODY L_BLD #define DANGER BR_RED #define L_BLD BEIGE #define M_BLD TAN #define D_BLD D_BROWN typedef struct { unsigned char c[36]; } uchar36; typedef struct { int x; int y; } intpair; typedef Byte TERRAIN; enum { BUILDING=0, RIVER, SWAMP, CRATER, ROAD, FOREST, RUBBLE, GRASS, HALFBUILDING, BOAT, DEEPSEA, REFBASE_T, PILLBOX_T, NUM_TERRAINS, TERRAIN_MASK = 0xF, TERRAIN_VISMINE = 0x40, TERRAIN_INVISMINE = 0x80, TERRAIN_ANYMINE = 0xC0 }; typedef union { Byte *byte; Word *word; DWord *dword; QWord *qword; OCTWord *oword; } MultiPtr; typedef union { PixMap p; BitMap b; } PixBitMap; typedef struct { Word word[16]; } Wordx16; global variables: DWord *mapchunky[BACKGROUND_CHARS]; // table of pointers to chunky BBC data Byte *mapchars[BACKGROUND_CHARS]; // table to pointers to rendered characters Byte *rendered_data; // pointer to buffer to hold data for // characters rendered to current bit depth short pixel_depth = 0; short pixel_depth_shift_equiv = 0; Rect GlobalMapViewRect; Byte * W_base; // baseAddr etc from current PixMap pointed to by drawto short W_top, W_left; long W_width, W_width2; short PixShift, pix_per_plotunit; long last_ctSeed = 0; short old_size = 0; PixBitMap *drawto, *direct_draw_option; PixBitMap directscreen; PixBitMap offscreen = { NULL, // baseAddr 0, // rowBytes { 0, 0, 3456, 3456 }, // bounds 0,0,0, // pmVersion, packType, packSize 0x00480000, 0x00480000, // 72x72 dpi chunky, 8, // pixelType, pixelSize 1, 8, // cmpCount, cmpSize (number of planes, and plane size) 0, // planeBytes (offset to next plane -- not applicable) NULL, // pmTable -- CTabHandle for device colour table 0 // pmReserved }; CTabHandle myowncolourtable; CTAB ctab; PaletteHandle mainpalette; short hasColorQD, do_direct_draw = FALSE; Before continuing, it is important to discuss how the images actaully make it to the screen. Bolo needs only to draw about 15x15=225 squares, and the memory for an offscreen bitmap (oops, pixmap) is not outrageous. BME would need to maintain the whole map, which can be as large as 15M. This clearly cannot be done. Therefore BME draws as it goes. The other point which needs clarifying concerns drawing directly to screen memory. Bolo has this option, but I don't know enough to be able to decide when this can and can't be done. Bolo checks for things like: * graphics area all on one screen * at supported pixel depth (1,2,4,8) * 32-bit addressing on if memory on NuBus card etc. If not, it draws to an offscreen area and copybits it into place. I set upon a solution whereby I create an offscreen pixmap the size of one horizontal row and as wide as the map, I draw one row at a time, and then copybits it. This seems a reasonable compromise. If you change this, be sure to change the initialization values for the offscreen pixmap (this caught me for an evening). Run at beginning of program. Be sure to include resources: ROMd 128 pltt 128 and set hasColorQD. (The majority of the comments from here on out are Stuart's.) /* -------------------- initialize ------------------------ */ #define mctL1 ((Byte*)*bbcrom + 0x60 + BBC_MAP_CHARS*0) #define mctL2 ((Byte*)*bbcrom + 0x60 + BBC_MAP_CHARS*1) #define mctL3 ((Byte*)*bbcrom + 0x60 + BBC_MAP_CHARS*2) #define mctL4 ((Byte*)*bbcrom + 0x60 + BBC_MAP_CHARS*3) #define mctH1 ((Byte*)*bbcrom + 0x60 + BBC_MAP_CHARS*4) #define mctH2 ((Byte*)*bbcrom + 0x60 + BBC_MAP_CHARS*5) #define mctH3 ((Byte*)*bbcrom + 0x60 + BBC_MAP_CHARS*6) #define mctH4 ((Byte*)*bbcrom + 0x60 + BBC_MAP_CHARS*7) void init_graphics(void) { long test; Rect r; Handle bbcrom; short i, row, max_depth = hasColorQD ? 8 : 1; DWord *chunkptr = (DWord*) NewPtr((BBC_MAP_CHARS) * 64L); rendered_data = (Byte *) NewPtr((BACKGROUND_CHARS) * ((long) 32) * max_depth); if (rendered_data == NULL) bug_alert("\prendered_data not allocated"); offscreen.b.rowBytes = 216*2 * max_depth; offscreen.b.baseAddr = (Ptr) NewPtr(((long) offscreen.b.rowBytes)*16); if (hasColorQD) { offscreen.b.rowBytes += 0x8000; mainpalette = (PaletteHandle) GetNewPalette(128); myowncolourtable = (CTabHandle) NewHandle(sizeof(ColorTable)); Palette2CTab(mainpalette, myowncolourtable); } // generate Mac chunky pixel data for each map square character bbcrom = GetResource('ROMd', 128); for (i=0; i>4, left[row+8]>>4, right[row]>>4, right[row+8]>>4); Word green = four4s(left[row] , left[row+8] , right[row] , right[row+8] ); if (chnum == 0x64) green |= cyan; // hack to turn BBC cyan mines into yellow // (helps them show up better in monochrome) for (i=0; i<16; i++) { answer = answer << 1 | (cyan & 1); cyan >>= 1; answer = answer << 1 | (green & 1); green >>= 1; } return(~answer); // Swap BBC (black zero) to Mac (white zero) format } There are two types of data, DWord *mapchunky[] and Byte *mapchars[]. The mapchunky is the raw data after begin processed by these init. routines. The mapchars are the results after converting for the pixel depth in use. /* -------------------- check drawing environment ------------------------ */ GDHandle GetBestDevice(Rect *rect) { Rect dummy; int bestscore = 0; GDHandle best = GetMainDevice(); // just in case none are picked up GDHandle dev = GetDeviceList(); while (dev) { if (TestDeviceAttribute(dev, screenDevice) && TestDeviceAttribute(dev, screenActive) && SectRect(rect, &(*dev)->gdRect, &dummy)) { int score = (*(*dev)->gdPMap)->pixelSize; // 256 bonus points for colour screens (gdDevType == hasColour) if (TestDeviceAttribute(dev, gdDevType)) score += 256; if (bestscore < score) { bestscore = score; best = dev; } } dev = GetNextDevice(dev); } return(best); } GDHandle pick_monitor(short x, short y, short *pixdepth, short *constrain_unit) { GDHandle gdevice; PixMapPtr gdevpmapPtr; GlobalMapViewRect = windowPtr->portRect; OffsetRect(&GlobalMapViewRect, x, y); if (!hasColorQD) { direct_draw_option = &directscreen; *pixdepth = 1; *constrain_unit = 16; directscreen.b = (windowPtr)->portBits; directscreen.p.bounds.left -= windowPtr->portRect.left; directscreen.p.bounds.top -= windowPtr->portRect.top; return(NULL); } else { direct_draw_option = &directscreen; *pixdepth = 1; *constrain_unit = 16; gdevice = GetBestDevice(&GlobalMapViewRect); gdevpmapPtr = *((*gdevice)->gdPMap); directscreen.p = *gdevpmapPtr; directscreen.p.bounds.left -= GlobalMapViewRect.left; directscreen.p.bounds.top -= GlobalMapViewRect.top; offscreen.p.pixelSize = offscreen.p.cmpSize = gdevpmapPtr->pixelSize; offscreen.p.pmTable = gdevpmapPtr->pmTable; direct_draw_option = &offscreen; // dont poke directly to screen // offscreen.p.pixelSize = offscreen.p.cmpSize = 8; offscreen.p.pmTable = myowncolourtable; if (gdevpmapPtr->pixelType != chunky || gdevpmapPtr->pixelSize > 8 || gdevpmapPtr->cmpCount > 1) { // I do not understand this new pixelmap format offscreen.p.pixelSize = offscreen.p.cmpSize = 8; offscreen.p.pmTable = myowncolourtable; } *pixdepth = offscreen.p.pixelSize; if (*pixdepth > 1) *constrain_unit = 32 / *pixdepth; return(gdevice); } } // This routine is called at startup, and then every time the drawing environment may have changed // -- window being moved, or monitor bit-depth change -- signaled by a window update event. void check_drawing_environment(void) { Point WPos; GDHandle gdevice; short new_pixel_depth, constrain_unit; SetPort(windowPtr); WPos.h = 0; WPos.v = 0; LocalToGlobal(&WPos); gdevice = pick_monitor(WPos.h, WPos.v, &new_pixel_depth, &constrain_unit); while (constrain_unit && (WPos.h & (constrain_unit - 1))) { // align the window, and try again to find the best monitor MoveWindow(windowPtr, ((WPos.h + (constrain_unit>>1)) & -constrain_unit),WPos.v, FALSE); WPos.h = 0; WPos.v = 0; LocalToGlobal(&WPos); gdevice = pick_monitor(WPos.h, WPos.v, &new_pixel_depth, &constrain_unit); } // if colour table changed then force a new "render_characters" below if (hasColorQD && last_ctSeed != (*direct_draw_option->p.pmTable)->ctSeed) { last_ctSeed = (*direct_draw_option->p.pmTable)->ctSeed; pixel_depth=0; } if ((pixel_depth != new_pixel_depth) || (size != old_size)) { static short log2[9] = { 0,0,1,0,2,0,0,0,3 }; static short pix_per_plotunit_table[4] = { 16, 16, 8, 1 }; GDHandle stash_gdevice; if (new_pixel_depth<1 || new_pixel_depth>8) bug_alert("\pIllegal pixel depth"); pixel_depth = new_pixel_depth; old_size = size; pixel_depth_shift_equiv = log2[pixel_depth]; pix_per_plotunit = pix_per_plotunit_table[pixel_depth_shift_equiv]; PixShift = 3 - pixel_depth_shift_equiv; if (gdevice) { stash_gdevice = GetGDevice(); SetGDevice(gdevice); } render_characters(); if (gdevice) SetGDevice(stash_gdevice); } } These I have more or less left intact, except to comment out/delete/insert some lines related to never drawing directly to screen. Note the comment above check_drawing_environment(). Whenever the drawing environment changes (including at the begining of the program), the mapchars must be re-rendered from the chunky data. I have made a couple of changes here. The case(size) statement related to colors (set_colour_tables()) is my own work. Bolo graphics are what I have as size=16. However, to draw at smaller sizes, I draw into my normal pixmap and then copybits it, allowing copybits to reduce from a larger Rect to a smaller. This it does by using evry other or every fourth pixel. To make more clear what the terrains were, I adapted the colors. The most notable changes were: * buildings use 3 shades of tan, but the smaller sizes drew only the darkest. So I substituted lighter tans for the darker * roads, where the center line was removed * swamp showed up as grass, so one of the unused colors was changed to a cross between grass and water and used for small_swamp. Therefore, the pltt #128 resource from Bolo and BME are NOT identical. /* -------------------- convert mapchunky -> macchars ------------------------ */ #pragma mark - void render_characters(void) { short i, row; CTAB Bcolour_switch_table[BACKGROUND_CHARS]; MultiPtr p = { NULL }; p.byte = rendered_data; for (i=0; i=4) set_colour_tables(Bcolour_switch_table); for (i=0; i SWAMP(4) = (WATER + GRASSC)/2, RED, M_GREY not used */ CTAB set_ctab(short a, short b, short c, short d) { RGBColor rgb; CTAB answer; answer.colour[0] = a; answer.colour[1] = b; answer.colour[2] = c; answer.colour[3] = d; return(answer); } void set_colour_tables(CTAB *back) { // CTAB shells = set_ctab(WHITE, 0, M_GREY, ORANGE); CTAB standard, building, road, deepsea, bridge, grass, swamp, forest; CTAB mine, mine_mask, mine_black, mine_mask2; standard = set_ctab(YELLOW, WATER, GRASSC, BLACK); building = set_ctab(D_BLD, M_BLD, L_BLD, BLACK); deepsea = set_ctab(0, WATER, 0, BLUE); grass = set_ctab(0, 0, GRASSC, BLACK); forest = set_ctab(LIME, 0, GRASSC, BLACK); mine_mask = set_ctab(WHITE, 0, 0, BLACK); road = set_ctab(L_GREY, TAN, GRASSC, BLACK); bridge = set_ctab(L_GREY, WATER, GRASSC, BLACK); swamp = standard; mine = set_ctab(YELLOW, 0, 0, 0); mine_black = set_ctab(0, 0, 0, BLACK); mine_mask2 = set_ctab(15, 0, 0, 0); switch (size) { case 4: road = set_ctab(BLACK, TAN, GRASSC, BLACK); bridge = set_ctab(BLACK, WATER, GRASSC, BLACK); building = set_ctab(M_BLD, L_BLD, L_BLD, BLACK); swamp = set_ctab(ORANGE, ORANGE, ORANGE, ORANGE); mine = set_ctab(0, 0, 0, 0); mine_black = set_ctab(0, 0, 0, 0); mine_mask2 = set_ctab(15, 0, 0, 15); break; case 8: road = set_ctab(BLACK, TAN, GRASSC, BLACK); bridge = set_ctab(BLACK, WATER, GRASSC, BLACK); building = set_ctab(M_BLD, L_BLD, L_BLD, BLACK); break; case 16: break; } back[0x00] = building; back[0x10] = standard; // rivers and swamps back[0x20] = swamp; // swamps and rivers back[0x30] = set_ctab(TAN, BROWN, GRASSC, BLACK); // crater back[0x40] = road; back[0x50] = forest; back[0x60] = grass; back[0x61] = road; back[0x64] = mine; back[0x65] = mine_mask; back[0x66] = road; back[0x68] = set_ctab(L_BLD, M_BLD, GRASSC, BLACK); // rubble back[0x69] = road; back[0x6A] = deepsea; back[0x6B] = road; back[0x70] = building; back[0x90] = set_ctab(WHITE, WATER, GRASSC, BLACK); // moored boats back[0xA0] = deepsea; back[0xB0] = set_ctab(YELLOW, 0, WHITE, BLACK); // neutral base back[0xB1] = bridge; back[0xB5] = set_ctab(YELLOW, 0, LIME, BLACK); back[0xB6] = bridge; back[0xB7] = set_ctab(YELLOW, 0, DANGER, BLACK); // hostile base back[0xB8] = bridge; back[0xBA] = set_ctab(YELLOW, 0, WHITE, BLACK); // dead base back[0xBB] = bridge; back[0xC0] = set_ctab(L_GREY, DANGER, PBODY, BLACK);// dead hostile pillbox back[0xC1] = set_ctab(DANGER, DANGER, PBODY, BLACK);// hostile pillbox back[0xD0] = standard; // man pointer arrows back[0xEE] = mine_black; back[0xEF] = mine_mask2; back[0xF0] = set_ctab(L_GREY, LIME, PBODY, BLACK); // dead friendly pillbox back[0xF1] = set_ctab(LIME, LIME, PBODY, BLACK); // friendly pillbox } // 0->white 1,2->grey, 3->black void conv_mono(register DWord d, Word *answer, int const row) { register int i=15; register Word a = 0; register int thresh = (row & 1) * 2; for (i=15; i>=0; i--) { a<<=1; if ((d & 3) > thresh) a |= 1; thresh ^= 2; d>>=2; } *answer = a; } void conv_2bit(register DWord d, DWord *answer) { register int i; register DWord a = 0; for (i=15; i>=0; i--) { a<<=2; a |= (d & 3); d>>=2; } *answer = a; } void conv_4bit(register DWord d, QWord *answer, QWord *c_mask) { register int i; register Byte *ptr = &answer->byte[0]; for (i=7; i>=0; i--) { *ptr = ctab.colour[d&3] << 4; d>>=2; *ptr++ |= ctab.colour[d&3]; d>>=2; } if (c_mask) { answer->dword[0] &= c_mask->dword[0]; answer->dword[1] &= c_mask->dword[1]; } } void conv_8bit(register DWord d, OCTWord *answer, OCTWord *c_mask) { register int i; register Byte *ptr = &answer->byte[0]; for (i=15; i>=0; i--) { *ptr = ctab.colour[d&3]; d >>= 2; ptr++; } if (c_mask) { answer->dword[0] &= c_mask->dword[0]; answer->dword[1] &= c_mask->dword[1]; answer->dword[2] &= c_mask->dword[2]; answer->dword[3] &= c_mask->dword[3]; } } Whew! Lots of bit-manipulating. Now we're ready to draw. Not quite. If drawing directly to screen we must shield the cursor, and if not we must copybits. /* -------------------- begin/end drawing ------------------------ */ void notify_preparing_drawing(void) { if (hasColorQD && last_ctSeed != (*direct_draw_option->p.pmTable)->ctSeed) { check_drawing_environment(); } // if (do_direct_draw && active && size == 16) // drawto = direct_draw_option; // else drawto = &offscreen; } void notify_beginning_drawing(void) { Point zipPoint; zipPoint.v = windowPtr->portRect.top; zipPoint.h = windowPtr->portRect.left; if (drawto == &directscreen) ShieldCursor(&GlobalMapViewRect, zipPoint); W_base = (Byte *) drawto->b.baseAddr; W_top = drawto->b.bounds.top; W_left = drawto->b.bounds.left; W_width = drawto->b.rowBytes & 0x7FFF; W_width2 = W_width - (2<portBits),&sr,&dr,srcCopy,NULL); // This is legal because of a special CopyBits hack to use PixMaps } } If the machine has 32-bit QD, then Stuart turned on dithering for copybits. I didn't want the hassle of checking. Now we're ready to draw. /* -------------------- drawing routines ------------------------ */ void plotmapchar(VIEW_X vx, VIEW_Y vy, Word mapchar) { register Byte *data = mapchars[mapchar & 0xFF]; register Byte *place; if (drawto == &directscreen) { place = W_base + (((vx - xx)<<4)-W_left >> PixShift) + (((vy-yy)<<4)-W_top) * W_width; } else { place = W_base + (((vx - xx)<<4)-W_left >> PixShift); } asm { moveq #15, d0 move.w pixel_depth_shift_equiv, d1 tst.w mapchar ; Is there a mine here? bmi.s @m cmpi.w #0, d1 ; NO. Check bit depth beq.s @1 cmpi.w #1, d1 beq.s @2 cmpi.w #2, d1 beq.s @4 cmpi.w #3, d1 beq.s @8 return @1 move.w (data)+, (place)+ add.l W_width2, place dbra.w d0, @1 return @2 move.l (data)+, (place)+ add.l W_width2, place dbra.w d0, @2 return @4 move.l (data)+, (place)+ move.l (data)+, (place)+ add.l W_width2, place dbra.w d0, @4 return @8 move.l (data)+, (place)+ move.l (data)+, (place)+ move.l (data)+, (place)+ move.l (data)+, (place)+ add.l W_width2, place dbra.w d0, @8 return ;------------------------------------------------------------- ; There is a mine here @m move.l mapchars[0xEF], a0 ; mine mask move.l mapchars[0x64], a1 ; mine yellow move.l a2,-(sp) move.l mapchars[0xEE], a2 ; mine_black cmpi.w #0, d1 beq.s @m1 cmpi.w #1, d1 beq.s @m2 cmpi.w #2, d1 beq.s @m4 cmpi.w #3, d1 beq.s @m8 move.l (sp)+,a2 return /* #define MINE_DRAW move.l (data)+, d1 \ or.l (a0)+, d1 \ and.l (a1)+, d1 \ move.l d1, (place)+ */ #define MINE_DRAW move.l (data)+, d1 \ and.l (a0)+, d1 \ or.l (a2)+, d1 \ or.l (a1)+, d1 \ move.l d1, (place)+ @m1 move.w (data)+, d1 or.w (a0)+, d1 and.w (a1)+, d1 move.w d1, (place)+ add.l W_width2, place dbra.w d0, @m1 move.l (sp)+,a2 return @m2 MINE_DRAW add.l W_width2, place dbra.w d0, @m2 move.l (sp)+,a2 return @m4 MINE_DRAW MINE_DRAW add.l W_width2, place dbra.w d0, @m4 move.l (sp)+,a2 return @m8 MINE_DRAW MINE_DRAW MINE_DRAW MINE_DRAW add.l W_width2, place dbra.w d0, @m8 move.l (sp)+,a2 return } } I have changed the mine-drawing stuff. I don't know how Stuart got it to work, but it didn't for me. To properly mask the mines, I used two unused characters, which I copied in the initialization section. ( mapchunky[0xEE] = mapchunky[0x65]; mapchunky[0xEF] = mapchunky[0x65]; ) Stuart's mine macro is commented out, and mine is in it's place. Anything related to mapchunky EE & EF is my doing. By the way, to see all of the characters go into BME and, with Bolo-like graphics showing and at least 16x16 boxes displayed, press control-t (not command-t). Neat, eh? Now we need a routine which adapts from map data (like road) to graphics characters (like 65 of 255). Word getmapchar(MAP_X x, MAP_Y y) { static Byte quick_returns[NUM_TERRAINS] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x60, 0x80, 0x00, 0x00, 0x0F, 0x0F }; static Byte adjval[NUM_TERRAINS] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x00, 0x01, 0x02, 0x80, 0x10, 0x00 }; // BLD RIVER SWAMP CRTER ROAD FREST RBBLE GRASS HALFB BOAT DEEPS BASE PILL // GRASS has no adjacency, nor do bases or pillboxes; // HALFBUILDING is same as BUILDING; BOAT is same as water; BASE is same as ROAD static Byte possadj[NUM_TERRAINS] = { 0x41, // BUILDING rubble or any other building 0x9A, // RIVER deepsea, road, crater or river 0x86, // SWAMP deepsea, swamp or river 0x8A, // CRATER deepsea, crater or river 0x10, // ROAD road only 0x20, // FOREST forest only 0x00, // RUBBLE - 0x00, // GRASS - 0x00, // HALFBUILDING - 0x8B, // BOAT deepsea, crater, river or building 0x80, // DEEPSEA deepsea only 0x00, // BASE - 0x00, // PILLBOX - }; Byte watermask = 0, adj4 = 0, adj8 = 0; TERRAIN a, t = getmapcell(x,y); // simple characters with no adjacency rules if (t == RUBBLE ) return(0x68); if (t == GRASS ) return(0x60); if (t == HALFBUILDING) return(0x80); if (t == REFBASE_T ) return(0xB0); // neutral, B7=hostile, B5=friendly if (t == PILLBOX_T ) { a = find_pill(x,y); return(0xC0 + *(pinfo+5*a+3)); // += x30 if friendly } a = getmapcell(x-1,y ); if is_wet(a) watermask |= 8; if (adjval[a] & possadj[t]) adj4 |= 8; a = getmapcell(x ,y+1); if is_wet(a) watermask |= 4; if (adjval[a] & possadj[t]) adj4 |= 4; a = getmapcell(x+1,y ); if is_wet(a) watermask |= 2; if (adjval[a] & possadj[t]) adj4 |= 2; a = getmapcell(x ,y-1); if is_wet(a) watermask |= 1; if (adjval[a] & possadj[t]) adj4 |= 1; // all except ROAD and BUILDING have 4-connected adjacency rules if (t != ROAD && t != BUILDING) return(t<<4 | adj4); // ROAD and BRIDGE can have 8-connected adjacency rules if (t == ROAD && watermask) { // top nibble B means this is char; top nibble 0 means check further static Byte bridgechar[16] = { 0x00, 0x04, 0x08, 0x0C, 0x01, 0xBE, 0x09, 0xBE, 0x02, 0x06, 0xBF, 0xBF, 0x03, 0xBE, 0xBF, 0xBD }; if (bridgechar[watermask] & 0xF0) return(bridgechar[watermask]); if ((bridgechar[watermask] & adj4) == bridgechar[watermask]) return(bridgechar[watermask] | 0xB0); } if ((adj4 & 0x9) == 0x9 && (adjval[getmapcell(x-1,y-1)] & possadj[t])) adj8 |= 8; if ((adj4 & 0xC) == 0xC && (adjval[getmapcell(x-1,y+1)] & possadj[t])) adj8 |= 4; if ((adj4 & 0x6) == 0x6 && (adjval[getmapcell(x+1,y+1)] & possadj[t])) adj8 |= 2; if ((adj4 & 0x3) == 0x3 && (adjval[getmapcell(x+1,y-1)] & possadj[t])) adj8 |= 1; if (!adj8) return(t<<4 | adj4); // no diagonal adjacency: normal character if (t == ROAD) return(0x60 | adj4); // lineless road ('car park') // OK, clever stuff here; Building with diagonal adjacency if (adj4 == 0x3) return(0x70); // 4 orientations of L shape if (adj4 == 0x6) return(0x74); if (adj4 == 0x9) return(0x78); if (adj4 == 0xC) return(0x7C); if (adj4 == 0xF) return(0x80 | adj8); // X building with flat bits if (adj4 == 0x7) return(0x70 + adj8); // adj8 = 1,2,3 if (adj4 == 0xE) return(0x74 + (adj8>>1)); // adj8 = 2,4,6 if (adj4 == 0xD) return(0x78 + (adj8>>2)); // adj8 = 4,8,C // for this case adj8 = 8,1,9 so if we just shift we would lose a bit if (adj8 & 1) adj8 |= 0x10; // 'rotate' the lost back in at top if (adj4 == 0xB) return(0x7C + (adj8>>3)); // adj8 = 8, 10, 18 } This routine expects to call certain other routines to get info, so here we adapt from my calls to Stuart's. Byte getmapcell(MAP_X x, MAP_Y y) { Byte k; k = get_box(x,y); if (k<10) return(k); if (k<16) return(k-8); if (k==23 || k==24) return(k-12); if (k==0xff) return(10); bug_alert("\pbad return from get_box"); } Byte mine_cell(MAP_X x, MAP_Y y) { Byte k; k = get_box(x,y); if (k>9 && k<16) return(TRUE); else return(FALSE); } Let's draw the map! void draw_stuart() { short x, y; Word c; SetPort(windowPtr); SetCursor(watch); DrawGrowIcon(windowPtr); DrawControls(windowPtr); notify_preparing_drawing(); for (y=yy;y<(yy+dheight);y++) { notify_beginning_drawing(); for (x=xx;x<(xx+dwidth);x++) { c = getmapchar(x, y); if (mine_cell(x,y)) c |= 0x8000; plotmapchar(x,y,c); } notify_finished_drawing(xx,y,dwidth); } PenNormal(); set_mode(-1); SetCursor(&arrow); } And a single box (for when edited): void draw_box_stuart(short x, short y) { Word c; notify_preparing_drawing(); notify_beginning_drawing(); c = getmapchar(x, y); if (mine_cell(x,y)) c |= 0x8000; plotmapchar(x,y,c); notify_finished_drawing(x,y,1); } Printing works much the same. gmode=0: Box by box, set a Rect and paint it. Requires on the order of 20-25 minutes for a laser printer to print a page. gmode=1: Set a Rect to the portion of the offscreen bitmap which is visible and copybits it to the printer port. gmode=2: Row by row, for the part of the row visible on this page, draw to the offscreen pixmap and copybits it to the printer port. The problem here is that I DON'T check the printing environment. If the screen is in 8 bit mode, then BME prints in 8 bit mode, which is not appropriate for most printers. The user must set the monitor to match the printer. This is a problem, but given the terminal nature of BME it didn't seem worth fixing. #include "PrintTraps.h" #define min(A,B) ((A)<(B) ? (A) : (B)) typedef union { PixMap p; BitMap b; } PixBitMap; static THPrint hPrint = NULL; char printing = FALSE; TPPrPort printPort; /* -------------------- printing ------------------------ */ void page_setup() { PrOpen(); if (hPrint == NULL) PrintDefault(hPrint = (TPrint **) NewHandle(sizeof(TPrint))); PrStlDialog(hPrint); PrClose(); } void print() { GrafPtr savePtr; short i, j, copies, page, boxesdone, my = 1, printError; short stxx, styy, stdwidth, stdheight; long mem_available; Rect printRect, dr, sr; Ptr oldbitmap = NULL, newbitmap = NULL; TPrStatus prStatus; GetPort(&savePtr); HideWindow(windowPtr); PrOpen(); if (PrError() == noErr) { if (hPrint == NULL) PrintDefault(hPrint = (TPrint **) NewHandle(sizeof(TPrint))); PrValidate(hPrint); } if (PrError() == noErr && PrJobDialog(hPrint)) { SetCursor(watch); printing = TRUE; stxx = xx; styy = yy; stdwidth = dwidth; stdheight = dheight; copies = (*hPrint)->prJob.iCopies; for (copies = 1; copies>0;copies--) { printPort = PrOpenDoc(hPrint,NULL,NULL); printRect = (**hPrint).prInfo.rPage; SetPort(printPort); PenNormal(); page = boxesdone = yy = 0; dheight = (printRect.bottom-printRect.top)/size; do { dwidth = (printRect.right-printRect.left)/size; xx = 0; do { if (xx+dwidth > width) dwidth = width-xx; if (yy+dheight > height) dheight = height-yy; page++; if ((page >= (*hPrint)->prJob.iFstPage) && (page <= (*hPrint)->prJob.iLstPage) && (PrError() == noErr)) { PrOpenPage(printPort,NULL); if ((PrError() == noErr) && (gmode == 2) && (pixel_depth > 1) && (printPort->gPort.portBits.rowBytes > 0)) { // render_print_bw(); Alert(199,NULL); PrSetError(iPrAbort); } if (PrError() == noErr) { // ----------------------------------------------- #pragma mark PREP if (page == 1 && gmode == 2) { mem_available = CompactMem(1507000) - 16384; i = mem_available / offscreen.b.rowBytes / 16; if (i>1) { if (i > dheight) i = dheight; newbitmap = NewPtr(i * offscreen.b.rowBytes * 16); if (newbitmap != NULL) { oldbitmap = offscreen.b.baseAddr; offscreen.b.baseAddr = newbitmap; my = i; debug_long = (long) my; } } } // ----------------------------------------------- SetPort(printPort); switch (gmode) { #pragma mark IMAGE // ----------------------------------------------- case 0: for (j=yy;j<(yy+dheight);j++) { for (i=xx;i<(xx+dwidth);i++) { draw_box(i,j,1); boxesdone++; } } break; // ----------------------------------------------- case 1: SetRect(&sr,xx*size,yy*size, (xx+dwidth)*size,(yy+dheight)*size); SetRect(&dr,0,0,dwidth*size,dheight*size); CopyBits(&(ofsptr->portBits),&(printPort->gPort.portBits), &sr,&dr,srcCopy,NULL); boxesdone += (dwidth*dheight); break; // ----------------------------------------------- case 2: j = yy; do { i = min(yy+dheight-j,my); i = print_row_stuart(j,i); j += i; boxesdone += i * dwidth; } while (jlast xx += dwidth; } while (xxgPort.portBits),&sr,&dr,srcCopy,NULL); return(ny); } I do not know if I have handled errors correctly, and I have no print dialog, etc (didn't like the idea of watching for cancel events - I'm a small time hacker, remember). But the output is virtually identical to the screen and reasonably fast. I hope this helps. The monochrome graphics are probably as good as can be done. The color graphics really needs a 68040 for zoomed out modes like size=4. By drawing directly to screen, however, I believe that they can be sped up by a factor of 3-5 (based on profiling).