|
|
1.1 ! root 1: /************************************************************* ! 2: ! 3: This program implements a tower of hanoi program. This ! 4: sample app was written to demonstrate the use of a multi- ! 5: threaded program. The main thread handles the PM interface, ! 6: the second thread is started to do the recursive execution ! 7: of the hanoi algorithm. ! 8: ! 9: Created by Microsoft, IBM Corporation 1990 ! 10: ! 11: DISCLAIMER OF WARRANTIES. The following [enclosed] code is ! 12: sample code created by Microsoft Corporation and/or IBM ! 13: Corporation. This sample code is not part of any standard ! 14: Microsoft or IBM product and is provided to you solely for ! 15: the purpose of assisting you in the development of your ! 16: applications. The code is provided "AS IS", without ! 17: warranty of any kind. Neither Microsoft nor IBM shall be ! 18: liable for any damages arising out of your use of the sample ! 19: code, even if they have been advised of the possibility of ! 20: such damages. ! 21: ! 22: Procedures in this file: ! 23: main() Sets up the PM environment and heap and ! 24: calls the main window procedure ClientWndProc ! 25: ClientWndProc() Handles the main window messages ! 26: CalcThread() Sets up and terminates the secondary thread ! 27: DrawDisk() Draws or erases a disk on one of the poles ! 28: MoveDisk() Moves a disk from one pole to another ! 29: Hanoi() Recursive alogrithm for tower of hanoi prog ! 30: EnableMenuItem() Activates/deactivates a menu choice ! 31: EntryFldDlgProc() Handles the set number of disks dialog box ! 32: SetupTowers() Sets up the global tower data ! 33: ! 34: **************************************************************/ ! 35: ! 36: #include "hanoi.h" ! 37: ! 38: /********************* GLOBALS *******************************/ ! 39: ! 40: CHAR szClientClass[] = "Hanoi"; ! 41: ! 42: /* Note that this use of global data precludes multiple windows ! 43: of hanoi running at the same time. Thus, from an object- ! 44: oriented perspective, this is less than desireable and the ! 45: data should be passed into the window, rather than used ! 46: explicitly. */ ! 47: ! 48: BYTE abTowers[3][MAXDISKCNT]; /* Used to hold disk numbers on each post */ ! 49: BYTE abTowersNdx[3]; /* Current number of disks on each post */ ! 50: BYTE cTowerSize = DEFAULTSIZE; /* Holds the total number of disks */ ! 51: USHORT ausPolePos[3]= { POSTOFFSET, /* Holds offset drawing information */ ! 52: POSTOFFSET + POSTSPACE, ! 53: POSTOFFSET + 2*POSTSPACE }; ! 54: ULONG ulIterations; ! 55: TID tidCalcThread; ! 56: BOOL fContinueCalc; ! 57: HWND hwndFrame, hwndClient; ! 58: ! 59: ! 60: /*************************************************************/ ! 61: ! 62: ! 63: /* ! 64: * Function name: main() ! 65: * ! 66: * Parameters: argc, argv. If the user places a number (1 thru MAXDISKCNT) ! 67: * on the command line, that number will become the default ! 68: * number of disks on the stack. Otherwise there will be ! 69: * DEFUALTSIZE disks initially. ! 70: * ! 71: * Returns: Always returns 0 ! 72: * ! 73: * Purpose: Parses the command line, sets up the PM environment, prepares ! 74: * the Tower arrays, calls the main window proc, handles the ! 75: * window's messages then cleans up and exits. ! 76: * ! 77: * Usage/Warnings: ! 78: * ! 79: * Calls: ClientWndProc() (thru PM) ! 80: */ ! 81: ! 82: int main(int argc, char *argv[]) ! 83: { ! 84: ! 85: HAB hab; ! 86: HMQ hmq; ! 87: QMSG qmsg; ! 88: ! 89: ULONG flFramefContinueCalcFlags = FCF_TITLEBAR | FCF_SYSMENU | FCF_MINBUTTON | ! 90: FCF_SHELLPOSITION | FCF_MENU | FCF_TASKLIST | ! 91: FCF_ICON | FCF_BORDER | FCF_ACCELTABLE; ! 92: ! 93: SHORT sHold; ! 94: ! 95: if(argc > 1) /* If command line arg, use as the initial number of disks */ ! 96: { ! 97: sHold = atoi(argv[1]); ! 98: if(sHold>0 && sHold<=MAXDISKCNT) ! 99: cTowerSize = (BYTE) sHold; ! 100: } ! 101: SetupTowers(); ! 102: ! 103: /* These PM calls should be error checked */ ! 104: hab = WinInitialize(0); ! 105: hmq = WinCreateMsgQueue(hab, 0); ! 106: ! 107: if(!WinRegisterClass(hab, szClientClass,(PFNWP)ClientWndProc,0L,0)) ! 108: { ! 109: WinAlarm(HWND_DESKTOP, WA_ERROR); /* Register failed */ ! 110: DosExit(EXIT_PROCESS,1); ! 111: } ! 112: ! 113: hwndFrame = WinCreateStdWindow(HWND_DESKTOP, WS_VISIBLE, ! 114: &flFramefContinueCalcFlags, szClientClass, NULL, ! 115: 0L, NULL, ID_RESOURCE, &hwndClient); ! 116: if(!hwndFrame) ! 117: { ! 118: WinAlarm(HWND_DESKTOP, WA_ERROR); /* Window create failed */ ! 119: DosExit(EXIT_PROCESS,1); ! 120: } ! 121: ! 122: while(WinGetMsg(hab,&qmsg,NULL,0,0)) /* Message loop */ ! 123: WinDispatchMsg(hab,&qmsg); ! 124: ! 125: WinDestroyWindow(hwndFrame); /* Clean up */ ! 126: WinDestroyMsgQueue(hmq); ! 127: WinTerminate(hab); ! 128: return 0; ! 129: } ! 130: ! 131: ! 132: /* ! 133: * Function name: ClientWndProc() ! 134: * ! 135: * Parameters: hwnd, msg, mp1, mp2. Standard PM Window Proc params. ! 136: * No user data is expected in the WM_CREATE. ! 137: * ! 138: * Returns: ! 139: * ! 140: * Purpose: Handles all the messages associated with the main window ! 141: * and calls the appropriate handling procedures. ! 142: * ! 143: * Usage/Warnings: Called only by main(). Note that when WM_PAINT executes, ! 144: * the secondary thread may change data during the update ! 145: * which may cause a problem. However, this is NOT a write ! 146: * conflict, as only 1 thread does the writing. ! 147: * ! 148: * Calls: DrawDisk(), CalcThread() (thru Thread), EntryFldDlgProc() (thru PM) ! 149: */ ! 150: ! 151: MRESULT EXPENTRY ClientWndProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2) ! 152: { ! 153: HPS hps; /* Handle for painting */ ! 154: RECTL rcl; /* Rectangle struct for painting */ ! 155: POINTL ptl; /* Point struct for painting */ ! 156: BYTE cPole,bCnt,bHeight,cnt; /* Utility variables */ ! 157: CHAR szMsg[MSGBUFSIZE]; /* sprintf buffer */ ! 158: ! 159: switch(msg) ! 160: { ! 161: case WM_PAINT: ! 162: hps = WinBeginPaint(hwnd,NULL,NULL); /* Get paint handle */ ! 163: WinQueryWindowRect(hwnd,&rcl); ! 164: ! 165: DrawRect(rcl.xLeft,rcl.yBottom, /* White out the screen */ ! 166: rcl.xRight,rcl.yTop,CLR_WHITE); ! 167: ! 168: /* Draw the base */ ! 169: DrawRect(BASEXOFFSET, BASEYOFFSET, ! 170: BASEXOFFSET+BASELEN-1, BASEYOFFSET+BASETHICK-1, ! 171: CLR_DARKGREEN); ! 172: ! 173: /* Draw the 3 posts */ ! 174: bHeight = cTowerSize*DISKSPACE + POSTEXTRAHT; ! 175: for(cnt=0;cnt<3;cnt++) ! 176: { ! 177: DrawRect(ausPolePos[cnt]-POSTHALF, BASEYOFFSET, ! 178: ausPolePos[cnt]-POSTHALF+POSTWIDTH,bHeight, ! 179: CLR_DARKGREEN); ! 180: } ! 181: ! 182: /* Place the appropriate disks on each pole */ ! 183: for(cPole=0;cPole<3;cPole++) ! 184: { ! 185: for(bCnt=0;bCnt<abTowersNdx[cPole];bCnt++) ! 186: { ! 187: DrawDisk(hps,cPole,bCnt,fDRAW); ! 188: } ! 189: } ! 190: ! 191: WinEndPaint(hps); ! 192: return 0L; ! 193: ! 194: case WM_COMMAND: ! 195: switch(SHORT1FROMMP(mp1)) ! 196: { ! 197: case IDM_START: ! 198: /* Set continuation fContinueCalc */ ! 199: fContinueCalc = TRUE; ! 200: ulIterations = 0; /* Zero iteration counter */ ! 201: ! 202: if (DosCreateThread (&tidCalcThread, CalcThread, ! 203: (ULONG)NULL, 0L, STACK)) ! 204: { ! 205: DosBeep (440, 175); ! 206: return 0L; ! 207: } ! 208: ! 209: /* Disallow menu items that could change data while the second ! 210: thread is running */ ! 211: EnableMenuItem(hwnd,IDM_START,FALSE); /* Disable Start & set */ ! 212: EnableMenuItem(hwnd,IDM_SET,FALSE); ! 213: EnableMenuItem(hwnd,IDM_STOP,TRUE); /* Enable Stop item */ ! 214: return 0L; ! 215: ! 216: case IDM_STOP: ! 217: fContinueCalc = FALSE; /* Notify thread to quit */ ! 218: return 0L; ! 219: ! 220: case IDM_SET: ! 221: if(WinDlgBox(HWND_DESKTOP, hwnd, /* Pop up the query/set box */ ! 222: (PFNWP)EntryFldDlgProc,NULL,ID_SETCOUNT,NULL)) ! 223: { ! 224: SetupTowers(); /* Reset towers */ ! 225: WinInvalidateRect(hwnd,NULL,FALSE); /* Force a redraw */ ! 226: } ! 227: return 0L; ! 228: break; ! 229: ! 230: case IDM_ABOUT: ! 231: WinDlgBox(HWND_DESKTOP, ! 232: hwnd, ! 233: (PFNWP)AboutDlgProc, ! 234: NULL, ! 235: IDD_ABOUTBOX, ! 236: NULL); ! 237: break; ! 238: ! 239: ! 240: default: ! 241: return WinDefWindowProc(hwnd, msg, mp1, mp2); ! 242: } ! 243: ! 244: case UM_CALC_DONE: ! 245: EnableMenuItem(hwnd,IDM_START,TRUE); /* Reenable Start & set */ ! 246: EnableMenuItem(hwnd,IDM_SET,TRUE); ! 247: EnableMenuItem(hwnd,IDM_STOP,FALSE); /* Disable stop */ ! 248: ! 249: sprintf(szMsg,"%lu disks were moved.",ulIterations); /* Print msg */ ! 250: WinMessageBox(HWND_DESKTOP, hwnd, szMsg, "Done!", 0, MB_OK); ! 251: ! 252: SetupTowers(); /* Reset towers */ ! 253: WinInvalidateRect(hwnd,NULL,FALSE); /* Force a screen redraw */ ! 254: return 0L; ! 255: ! 256: default: ! 257: return WinDefWindowProc(hwnd, msg, mp1, mp2); ! 258: } ! 259: } ! 260: ! 261: ! 262: /* ! 263: * Function name: CalcThread() ! 264: * ! 265: * Parameters: none ! 266: * ! 267: * Returns: VOID ! 268: * ! 269: * Purpose: Calls the recursive Hanoi with initial setting of 0,2,1 meaning ! 270: * from pole 0, to pole 2, using pole 1 as a temporary. Hanoi ! 271: * returns when finished, or the user says stop. This proc then ! 272: * sets a critical section so the posted message won't be handled ! 273: * until the thread is terminated. ! 274: * ! 275: * Usage/Warnings: No DosExitCritSec() is called since _endthread() supposedly ! 276: * clears the critical section when the thread is ! 277: * terminated. ! 278: * ! 279: * Calls: Hanoi() ! 280: */ ! 281: ! 282: VOID CalcThread () ! 283: { ! 284: HAB hab; ! 285: ! 286: hab = WinInitialize(0); /* Called to increase Ring 2 stack size */ ! 287: Hanoi(cTowerSize,0,2,1); /* Execute the recursive routine */ ! 288: WinTerminate(hab); ! 289: ! 290: DosEnterCritSec(); /* Set Crit so the UM_CALC_DONE isn't processed */ ! 291: /* until this thread has completely terminated */ ! 292: WinPostMsg(hwndClient,UM_CALC_DONE,NULL,NULL); /* Post done */ ! 293: ! 294: DosExit (EXIT_THREAD, 0); /* Terminate thread */ ! 295: } ! 296: ! 297: ! 298: /* ! 299: * Function name: DrawDisk() ! 300: * ! 301: * Parameters: hps is a handle to the main PS space. ! 302: * cPole is the pole (0-2) to draw the disk on. ! 303: * bLevel is the number of spaces from the bottom to draw disk. ! 304: * fDraw if =0, erase disk, if =1 draw disk. ! 305: * ! 306: * Returns: VOID ! 307: * ! 308: * Purpose: This routine takes a PS handle, the hanoi spindle and disk level ! 309: * and draws an appropriately sized disk. ! 310: * ! 311: * Usage/Warnings: Does not grab exclusive access to the screen before ! 312: * drawing which may cause a problem. ! 313: * ! 314: * Calls: ! 315: */ ! 316: ! 317: VOID DrawDisk(HPS hps,BYTE cPole, BYTE bLevel,BYTE fDraw) ! 318: { ! 319: USHORT usXstart,usYstart,usWidth; ! 320: POINTL ptl; ! 321: ! 322: usYstart = BOTDISKYPOS + DISKSPACE*bLevel; /* Calculate Bottom of disk */ ! 323: ! 324: usWidth = (MAXDISKWIDTH-MINDISKWIDTH)*abTowers[cPole][bLevel]/cTowerSize ! 325: + MINDISKWIDTH; /* Calculate the disk's width */ ! 326: ! 327: usXstart = ausPolePos[cPole] - usWidth/2; /* Center disk on pole */ ! 328: ! 329: if(fDraw == fDRAW) /* If we are to draw the disk */ ! 330: { ! 331: DrawRect(usXstart,usYstart,usXstart+usWidth, ! 332: usYstart+DISKTHICK-1,CLR_RED); ! 333: } ! 334: else /* We are to erase the disk, then redraw the pole */ ! 335: { ! 336: DrawRect(usXstart,usYstart,usXstart+usWidth, ! 337: usYstart+DISKTHICK-1,CLR_WHITE); ! 338: DrawRect(ausPolePos[cPole]-POSTHALF,usYstart, ! 339: ausPolePos[cPole]-POSTHALF+POSTWIDTH,usYstart+DISKTHICK-1, ! 340: CLR_DARKGREEN); ! 341: } ! 342: } ! 343: ! 344: ! 345: /* ! 346: * Function name: MoveDisk() ! 347: * ! 348: * Parameters: hps is a handle to the main PS space. ! 349: * bFrom is the spindle to take the top disk from. ! 350: * bTo is the spindle to place the disk on. ! 351: * ! 352: * Returns: VOID ! 353: * ! 354: * Purpose: This routine moves the top disk from the bFrom spindle to the top ! 355: * of the bTo spindle. ! 356: * ! 357: * Usage/Warnings: Does error checking for trying to move a disk from a ! 358: * spindle that does not have any disks on it. ! 359: * ! 360: * Calls: MoveDisk() ! 361: */ ! 362: ! 363: VOID MoveDisk(HPS hps,BYTE bFrom,BYTE bTo) ! 364: { ! 365: CHAR bTOSndx; /* Top of stack index */ ! 366: BYTE bDiskNum; /* Disk number to move */ ! 367: ! 368: bTOSndx = abTowersNdx[bFrom]-1; ! 369: if(bTOSndx < 0) ! 370: return; ! 371: ! 372: DrawDisk(hps,bFrom,bTOSndx,fERASE); /* Remove disk off from stack */ ! 373: ! 374: bDiskNum = abTowers[bFrom][bTOSndx]; /* Get move disk number */ ! 375: abTowersNdx[bFrom]--; /* Decrease count on from spindle */ ! 376: ! 377: bTOSndx = abTowersNdx[bTo]++; /* Offset to place the disk at */ ! 378: abTowers[bTo][bTOSndx] = bDiskNum; /* Place on stack in memory */ ! 379: ! 380: DrawDisk(hps,bTo,bTOSndx,fDRAW); /* Draw disk on the to stack */ ! 381: } ! 382: ! 383: ! 384: /* ! 385: * Function name: Hanoi() ! 386: * ! 387: * Parameters: pcp is a struct which contains the hwnd handle and the ! 388: * continue fContinueCalcFlag which is initially set to TRUE. ! 389: * bHeight is the number of disks in the from stack to move. ! 390: * bFrom is the from spindle number, 0-2. ! 391: * bTo is the to spindle number. ! 392: * bTemp is the temporary spindle number. ! 393: * ! 394: * Returns: VOID ! 395: * ! 396: * Purpose: This routine implements a recursive hanoi program that works as ! 397: * follows: By recursion, move all the disks, except for the ! 398: * bottom disk to the temporary stack. Then move the bottom ! 399: * disk to the target spindle. Now recursively move the stack ! 400: * on the temporary spindle to the target spindle. The limiting ! 401: * case is when Hanoi() is called with a bHeight of 0 in which ! 402: * case the depth recursion is terminated. ! 403: * ! 404: * Usage/Warnings: This routine checks the ->fContClac fContinueCalcFlag, which is set ! 405: * by the main thread when the user selects stop, to see if ! 406: * the user wishes to abort the algorithm. If so, it backs ! 407: * out and exits. ! 408: * ! 409: * Calls: MoveDisk() ! 410: */ ! 411: ! 412: VOID Hanoi(BYTE bHeight, BYTE bFrom, BYTE bTo, BYTE bTemp) ! 413: { ! 414: HPS hps; ! 415: ! 416: if(bHeight<=0 || !fContinueCalc) /* Exit up if no more disks or */ ! 417: return; /* the user said Stop */ ! 418: ! 419: Hanoi(bHeight-1,bFrom,bTemp,bTo); /* Move all but bottom disk */ ! 420: ! 421: if(!fContinueCalc) /* If user said to stop */ ! 422: return; ! 423: ! 424: /* Display bFrom -> bTo */ ! 425: hps = WinGetPS(hwndClient); ! 426: MoveDisk(hps,bFrom,bTo); /* Move the bottom disk */ ! 427: WinReleasePS(hps); ! 428: ulIterations++; ! 429: ! 430: Hanoi(bHeight-1,bTemp,bTo,bFrom); /* Move disks over */ ! 431: } ! 432: ! 433: ! 434: /* ! 435: * Function name: EnableMenuItem() ! 436: * ! 437: * Parameters: hwnd is a handle of the current window. ! 438: * sMenuItem is the ID of the item to Enable/Disable. ! 439: * fEnable will enable item if TRUE, otherwise disables it. ! 440: * ! 441: * Returns: VOID ! 442: * ! 443: * Purpose: This routine handles enabling/disabling of menu items. This ! 444: * is done by getting Parent and Menu hwnd handles then sending ! 445: * the appropriate message. ! 446: * ! 447: * Usage/Warnings: ! 448: * ! 449: * Calls: ! 450: */ ! 451: ! 452: VOID EnableMenuItem(HWND hwnd, SHORT sMenuItem, BOOL fEnable) ! 453: { ! 454: HWND hwndParent = WinQueryWindow(hwnd, QW_PARENT, FALSE); ! 455: HWND hwndMenu = WinWindowFromID(hwndParent, FID_MENU); ! 456: ! 457: WinSendMsg(hwndMenu, MM_SETITEMATTR, ! 458: MPFROM2SHORT(sMenuItem, TRUE), ! 459: MPFROM2SHORT(MIA_DISABLED, fEnable ? 0 : MIA_DISABLED)); ! 460: } ! 461: ! 462: ! 463: /* ! 464: * Function name: EntryFldDlgProc() ! 465: * ! 466: * Parameters: hwnd, msg, mp1, mp2. Standard PM Dialog Proc params. ! 467: * No user data is expected in the WM_CREATE. ! 468: * ! 469: * Returns: Terminates with a TRUE iff a new valid Tower Size has been entered. ! 470: * ! 471: * Purpose: Handles all the messages associated with the set entry field ! 472: * and calls the appropriate handling procedures. The purpose ! 473: * of this dialog box is to get a new number of disks for the ! 474: * hanoi routine. ! 475: * ! 476: * ! 477: * Usage/Warnings: If the value entered is valid, global cTowerSize is ! 478: * changed to the new value, and TRUE is returned. ! 479: * ! 480: * Calls: ! 481: */ ! 482: ! 483: MRESULT EXPENTRY EntryFldDlgProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2) ! 484: { ! 485: SHORT sNewSize; /* Holds new number of disks */ ! 486: ! 487: switch(msg) ! 488: { ! 489: case WM_INITDLG: ! 490: WinSendDlgItemMsg(hwnd, ID_ENTRYFLD,EM_SETTEXTLIMIT, /* Limit len */ ! 491: MPFROM2SHORT(2,0),NULL); ! 492: WinSetDlgItemShort(hwnd, ID_ENTRYFLD,(SHORT) cTowerSize,TRUE); ! 493: return 0L; /* Allow normal focus setting */ ! 494: ! 495: case WM_COMMAND: ! 496: switch(SHORT1FROMMP(mp1)) ! 497: { ! 498: case DID_OK: ! 499: WinQueryDlgItemShort(hwnd, ID_ENTRYFLD, ! 500: &sNewSize, TRUE); /* Get the short */ ! 501: if(sNewSize>0 && sNewSize<=MAXDISKCNT) /* Set new Tower size */ ! 502: { ! 503: cTowerSize = (BYTE) sNewSize; ! 504: WinDismissDlg(hwnd,TRUE); ! 505: } ! 506: else /* Invalid value */ ! 507: { ! 508: WinMessageBox (HWND_DESKTOP, hwndFrame, "You may only select up to 16 disks", ! 509: "OOPS! Too many disks!", ID_MSGBOX, MB_OK | ! 510: MB_ICONEXCLAMATION); ! 511: } ! 512: ! 513: return 0L; ! 514: ! 515: case DID_CANCEL: ! 516: WinDismissDlg(hwnd,FALSE); ! 517: return 0L; ! 518: ! 519: default: ! 520: return WinDefDlgProc(hwnd, msg, mp1, mp2); ! 521: } ! 522: ! 523: default: ! 524: return WinDefDlgProc(hwnd, msg, mp1, mp2); ! 525: } ! 526: } ! 527: ! 528: /* ! 529: * Function name: AboutDlgProc() ! 530: * ! 531: * Parameters: hwnd, msg, mp1, mp2. Standard PM Dialog Proc params. ! 532: * No user data is expected in the WM_CREATE. ! 533: * ! 534: * Returns: ! 535: * ! 536: * Purpose: Handles all the messages associated with the About Box ! 537: * ! 538: * ! 539: * Usage/Warnings: ! 540: * ! 541: * Calls: ! 542: */ ! 543: ! 544: MRESULT EXPENTRY AboutDlgProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2) ! 545: { ! 546: ! 547: switch(msg) ! 548: { ! 549: case WM_COMMAND: ! 550: WinDismissDlg(hwnd, TRUE); ! 551: break; ! 552: ! 553: default: ! 554: return WinDefDlgProc(hwnd, msg, mp1, mp2); ! 555: } ! 556: } ! 557: ! 558: /* ! 559: * Function name: SetupTowers() ! 560: * ! 561: * Parameters: None ! 562: * ! 563: * Returns: VOID ! 564: * ! 565: * Purpose: This routine initializes the global arrays that represent the ! 566: * hanoi stacks. This involves placing all the disks on the ! 567: * first peg, emptying the other 2 pegs and setting the associated ! 568: * counts. ! 569: * ! 570: * Usage/Warnings: Calling uses the global variable cTowerSize to determine ! 571: * how many disks there are. ! 572: * ! 573: * Calls: ! 574: */ ! 575: ! 576: VOID SetupTowers() ! 577: { ! 578: USHORT cnt; ! 579: ! 580: for(cnt=0;cnt<cTowerSize;cnt++) /* Setup the intial post with disks */ ! 581: abTowers[0][cnt] = cTowerSize-cnt-1; ! 582: ! 583: abTowersNdx[0] = cTowerSize; /* Set disk count for initial post */ ! 584: abTowersNdx[1] = abTowersNdx[2] = 0; /* Zero other post counts */ ! 585: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.