|
|
1.1 root 1: /***
2: * Title:
3: *
4: * LIFE - An OS/2 Game of Life
5: *
1.1.1.2 ! root 6: * Created by Microsoft Corp. 1987
1.1 root 7: *
8: * Description:
9: *
10: * This is a OS/2 implementation of the game of Life. It is designed
11: * to be bound to allow it to operate in both the protect mode of OS/2
12: * and in earlier MS-DOS versions. The program uses a mouse if one
13: * is installed, but one is not needed.
14: *
15: * Keyboard commands: B(lank) - redraw screen
16: * G(o) - step through generations until a key or
17: * button is hit
18: * H(alt) - freeze at current generation
19: * Q(uit) - exit program
20: * R(ead) - read a game board from the disk
21: * S(step) - advance a single generation
22: * W(rite) - write the current board to disk
23: * D(own-speed) - slow down GOs
24: * U(p-speed) - speed up GOs
25: *
26: */
27:
1.1.1.2 ! root 28: #define INCL_DOS /* includes all OS/2 calls in */
! 29: #define INCL_SUB /* bsedos.h and bsesub.h */
1.1 root 30:
1.1.1.2 ! root 31: #include <os2def.h> /* definitions for OS/2 calls and */
! 32: #include <bse.h> /* associated data structures */
1.1 root 33:
34: /* defines */
35: #define SCR_WID 80 /* screen buffer row width in bytes */
36: #define SIGNATURE 0x5342 /* 1st word of all files from this */
37:
38: /* these defines are all coordinates to put various messsages */
39: #define COM_ROW 23 /* alpha row for command line */
40: #define COM_COL 0 /* column for same */
41: #define PROMPT_ROW 24 /* alpha row for prompts */
42: #define PROMPT_COL 0 /* column for same */
43: #define GEN_COL 13 /* column to put generation number on*/
44: #define FILE_COL 37 /* column to put filespec */
45:
46: /* misc. defines */
47: #define FONT_ROW 8 /* pixel rows in the font */
48: #define BIT 8 /* number of bits in a byte */
49: #define BYTEINCOLS 8 /* no. of interal columns in a byte */
50:
51: /**** all functions in this file ******/
52: int main(); /* setup and master loop for life game*/
53: void blank(); /* blank internal and screen grid*/
54: void go(); /* steps through generations until a key is pressed*/
55: void halt(); /* does nothing */
56: void quit(); /* exit after prompting for certainty*/
57: void diskread(); /* read new grid from disk*/
58: int step(); /* advance one generation on screen and internally*/
59: void diskwrite(); /* write internal grid to disk*/
60: void up(); /* speeds up GO if possible */
61: void down(); /* slows down GO */
62: void beep(); /* beep the speaker for bad commands*/
63: void showingrid(); /* display internal grid on screen*/
64: int getfilespec(); /* get a file name from the user*/
65: void putgen(); /* puts the current generation counter on prompt line*/
66: void fill(); /* fill in a grid cell on the screen and internally*/
67: void remove(); /* clear a grid cell on the screen and internal grids*/
68: int kbdreadeventque(); /* simulates MouReadEventQue with the keyboard*/
69: void xorptr(); /* xors the mouse ptr on the screen*/
70: void gputs(); /* put a character string on the graphics screen*/
71: void gputchar(); /* put a character on the graphics screen*/
72: void anerror(); /* error handler*/
73: char gethit(); /* waits for a key hit or mouse button hit*/
74: void wait4release(); /* wait until mouse buttons are up before returning*/
75: void far pascal exitlife();/* exit program, resetting original screen mode*/
76:
77:
78: /* global data */
79: /* global variables for the internal and screen grids */
80: int InRow = 45; /* rows & columns of cells w/ default*/
81: int InCol = 80; /* in internal grid. Col must be /8*/
82: int ScrRow = 45; /* rows & column on screen grid */
83: int ScrCol = 79;
84: int SizeScrRow = 4; /* pixel rows per screen grid row */
85: int SizeScrCol = 8; /* pixels cols per screen grid column*/
1.1.1.2 ! root 86: PSZ InGrid; /* pointer to internal grid, a simple
1.1 root 87: * bit map of the space and cells */
1.1.1.2 ! root 88: PSZ InGrid2; /* pointer for secondary map used
1.1 root 89: * to calculate next generation */
90:
91: /* screen related global data */
1.1.1.2 ! root 92: static VIOMODEINFO Highres = {12, 3, 1, 80, 25, 640, 200}; /* 640x200 b/w*/
! 93: VIOMODEINFO Savemode = {12}; /* place to save old screen mode */
1.1 root 94: unsigned ScrSeg; /* screen buffer segment address */
95: char Cell[]={32,7}; /* blank for clear screen */
96: unsigned OddPage=0x2000; /* offset on Cga of odd row bit plane*/
97: /* should be 0 on non-Cga modes */
98: unsigned Cga=2; /* if using a Cga, screen adresses on
99: * must be divided by 2 because of
100: * the odd and even row bit planes
101: * if using non-Cga mode, should be 1*/
102: /* mouse related global data */
1.1.1.2 ! root 103: HMOU Mouse = 0; /* handle if mouse is present, else */
1.1 root 104: int MouBoundRow = COM_ROW*FONT_ROW+6; /* last row mouse is allowed on */
105: int MouBoundCol = 631; /* last column mouse is allowed on */
106:
107: /* misc. global data */
108: char Filespec[79-FILE_COL]; /* file name holder (allows default)*/
109: unsigned Generation; /* current generation count */
110: char Logo[] = " \
111: Microsoft LIFE"; /* logo used to clear prompt line */
112: int Slow=0; /* number of slow-down loops for Go */
113:
114: /* Data for commands. Column of command on COM_ROW for printed name,
115: * name for printing on command line, flag if not 0 then the command
116: * can be executed within a Go command without stopping execution,
117: * and the function that does the command. */
118: struct Commands {
119: int Col;
1.1.1.2 ! root 120: NPSZ Name;
1.1 root 121: char GoAble;
122: void (*Fun)();
123: } ComLine[] = { 0, "Command:", 0, beep,
124: 10, "Blank", 0, blank,
125: 16, "Go", 0, go,
126: 19, "Halt", 0, halt,
127: 24, "Quit", 0, quit,
128: 29, "Read", 0, diskread,
129: 34, "Step", 0, step,
130: 39, "Write", 0, diskwrite,
131: 46, "Down-speed", 1, down,
132: 57, "Up-speed", 1, 0,
133: 67, 0, 0
134: };
135:
136:
137: /*** main - setup and master loop for life game
138: *
139: * First this sets the screen mode, gets the address of the screen buffer,
140: * sets the ctrl-C handle to a routine to reset the screen mode on exit,
141: * and allocates the data structures for the internal representation of
142: * the life grid. The main loop of the program continuously reads from
143: * the keyboard and the mouse and translates the key or location of the
144: * mouse with the ComLine structure into a command to execute.
145: */
146: main () {
1.1.1.2 ! root 147: KBDKEYINFO kbd; /* return for KBD call */
! 148: VIOPHYSBUF get_phys; /* return for GetPhysBufData */
! 149: USHORT throwaway; /* for returns I don't use */
1.1 root 150: int i; /* just a counter */
151: unsigned far *ptr; /*used to clear internal grid*/
152:
153: /* data for the mouse (or keyboard emulator) */
154: int ptrrow = 86; /* current pointer position */
155: int ptrcol = 316;
1.1.1.2 ! root 156: USHORT status = 0x100; /* for MouSetDevStatus */
! 157: USHORT type=0; /* for no waits on mouse read */
! 158: MOUEVENTINFO event; /* return for mouse reads */
! 159: PTRLOC loc; /* data for MouSetPtrPos */
1.1 root 160:
161:
162: /* try to open and initialize mouse. If none installed, Mouse will
163: * stay 0 to show keyboard emulation must be used */
1.1.1.2 ! root 164: if (!MouOpen (0L, &Mouse)) /* get handle */
1.1 root 165: /* mouse is here and well, so initialize it */
1.1.1.2 ! root 166: MouSetDevStatus (&status, Mouse);
1.1 root 167:
168: /* get current screen mode and save it for restoring on exit */
1.1.1.2 ! root 169: if (VioGetMode (&Savemode, 0)) {
1.1 root 170: printf ("Error setting screen mode, exitting\n");
1.1.1.2 ! root 171: DosExit (EXIT_PROCESS, 0); /* exit if error */
1.1 root 172: }
173: /* set screen mode to 640x200 b/w (Cga high resolution */
1.1.1.2 ! root 174: if (VioSetMode (&Highres, 0)) {
1.1 root 175: printf ("Error setting screen mode, exitting\n");
1.1.1.2 ! root 176: DosExit (EXIT_PROCESS, 0); /* exit if error */
1.1 root 177: }
178:
179: /* set mouse pointer to middle of screen */
180: if (Mouse) {
1.1.1.2 ! root 181: loc.row=ptrrow;
! 182: loc.col=ptrcol;
! 183: MouSetPtrPos (&loc, Mouse);
1.1 root 184: }
185:
186: /* get screen buffer segment */
1.1.1.2 ! root 187: get_phys.pBuf=(PBYTE)0xb8000L;
! 188: get_phys.cb=16*1024L;
! 189: if (VioGetPhysBuf (&get_phys, 0)) {
1.1 root 190: /* if error here, restore screen and exit */
1.1.1.2 ! root 191: VioSetMode (&Savemode, 0);
1.1 root 192: printf ("Error accessing screen memory, exitting\n");
1.1.1.2 ! root 193: DosExit (EXIT_PROCESS, 0);
1.1 root 194: }
1.1.1.2 ! root 195: ScrSeg = get_phys.asel[0]; /* store in global */
1.1 root 196:
197: /* set ctrl-C to quit() to reset screen at even a break */
1.1.1.2 ! root 198: DosSetSigHandler (exitlife, (PFNSIGHANDLER FAR *)&throwaway,
! 199: &throwaway, 2, 1);
1.1 root 200:
201: /* allocate a segment to hold internal grid representation */
1.1.1.2 ! root 202: if (DosAllocSeg (InRow*InCol/BYTEINCOLS, (PSEL) &InGrid, 0))
1.1 root 203: /* from here on use anerror() to report errors */
204: anerror ("Error allocating memory", 1);
205: (long) InGrid *= 0x10000L; /* move selector to high word*/
206:
207: /* allocate a segment to hold second buffer used in stepping */
1.1.1.2 ! root 208: if (DosAllocSeg(InRow*InCol/BYTEINCOLS, (PSEL) &InGrid2, 0))
1.1 root 209: anerror ("Error allocating memory", 1);
210: (long) InGrid2 *= 0x10000L; /* move selector to high word*/
211:
212: /* blank internal grid and draw screen */
213: for (i=InRow*InCol/16, ptr=(unsigned far *) InGrid ; i--;)
214: *ptr++ = 0;
215: showingrid ();
216: xorptr (ptrrow, ptrcol); /* show cursor */
217:
218:
219: /* main input loop, polling mouse and keyboard */
220: while (1) {
221: /* try to get mouse event from keyboard or mouse */
1.1.1.2 ! root 222: kbd.chChar=0; /* clear key buffer residue */
1.1 root 223: kbdreadeventque (&event, ptrrow, ptrcol);
224: if (Mouse)
1.1.1.2 ! root 225: MouReadEventQue (&event, &type, Mouse);
1.1 root 226:
227: /* if any mouse-like events, do this */
1.1.1.2 ! root 228: if (event.fs) {
1.1 root 229: xorptr (ptrrow, ptrcol); /* hide cursor */
1.1.1.2 ! root 230: if (event.fs & 1+2+8) { /* if any motion */
1.1 root 231: /* update position, if off screen grid,ptr=max or min*/
1.1.1.2 ! root 232: ptrrow = event.row;
! 233: ptrcol = event.col;
1.1 root 234: if (ptrrow > MouBoundRow)
235: ptrrow = MouBoundRow;
236: if (ptrcol > MouBoundCol)
237: ptrcol = MouBoundCol;
238: }
239: /* if a button was hit */
1.1.1.2 ! root 240: if (event.fs & (2|4|8|16)) {
1.1 root 241: if (ptrrow < COM_ROW*FONT_ROW-SizeScrRow) { /* if on grid */
242: Generation=0; /* reset Gen if */
243: putgen(); /* grid modified*/
1.1.1.2 ! root 244: if (event.fs & (2 | 4)) /* if left down*/
1.1 root 245: fill (ptrcol/SizeScrCol, ptrrow/SizeScrRow);
1.1.1.2 ! root 246: else if (event.fs & (8 | 16)) /* if right */
1.1 root 247: remove (ptrcol/SizeScrCol, ptrrow/SizeScrRow);
248: }
249: else { /* on command line*/
250: /* if pointing at command word, execute it, highlighting */
251: for(i=0; ComLine[i].Fun != 0; i++)
252: if (ptrcol/BIT >= ComLine[i].Col &&
253: ptrcol/BIT < ComLine[i+1].Col-1) {
254: gputs(ComLine[i].Name,COM_ROW,ComLine[i].Col,0xff);
255: wait4release (); /* execute on release*/
256: (*(ComLine[i].Fun))();
257: if (ComLine[i].Fun) /*restore name if not removed*/
258: gputs(ComLine[i].Name,COM_ROW,ComLine[i].Col,0);
259: }
260: }
261: }
262: xorptr (ptrrow, ptrcol); /* show cursor */
263: }
264:
265: /* check keys, no wait */
1.1.1.2 ! root 266: KbdPeek (&kbd, 0);
! 267: if (kbd.chChar) { /* get key only if not regular char */
! 268: KbdCharIn (&kbd, 1, 0); /*get command*/
1.1 root 269: xorptr (ptrrow, ptrcol); /* avoid overwrites */
270: /* walk command structure and execute function, highlighting */
271: for(i=0; ComLine[i].Fun != 0; i++)
1.1.1.2 ! root 272: if ((kbd.chChar & 0xdf) == *(ComLine[i].Name)) {
1.1 root 273: gputs(ComLine[i].Name,COM_ROW,ComLine[i].Col,0xff);
274: (*(ComLine[i].Fun))();
275: if (ComLine[i].Fun) /*restore name if not removed*/
276: gputs(ComLine[i].Name,COM_ROW,ComLine[i].Col,0);
277: }
278: xorptr (ptrrow, ptrcol); /* restore pointer */
279: }
280: }
281: }
282:
283:
284: /*** blank - blank internal and screen grid
285: *
286: * Sets the internal grid to all 0's and calls draw_grid() to
287: * clear the screen and put up a screen grid. blank() then draws
288: * the command line and the program logo.
289: *
290: * Entry: ScrSeg = current screen segment
291: * InGrid points to internal grid defined by InCol and InRow
292: * Logo points to program logo that also clears the prompt line
293: *
294: * Exit: Generation = 0
295: *
296: * Calls: gputs(), putgen(), draw_grid [assembler routine]
297: */
298: void
299: blank (){
300: unsigned far *ptr;
301: int i;
302:
303: gputs (Logo, PROMPT_ROW, PROMPT_COL, 0);
304: gputs ("Are you sure?", PROMPT_ROW, PROMPT_COL, 0);
305: if (gethit () == 'Y') {
306: /* if yes, blank screen and internal grid */
307: draw_grid (); /* draw blank grid on screen */
308: for (i=InRow*InCol/16, ptr=(unsigned far *) InGrid ; i--;)
309: *ptr++ = 0; /* blank internal grid */
310: for (i=0; ComLine[i].Fun != 0; i++) /* print command line */
311: gputs (ComLine[i].Name, COM_ROW, ComLine[i].Col, 0);
312: Generation=0; /* reset generation count */
313: }
314: gputs (Logo, PROMPT_ROW, PROMPT_COL, 0); /* put on logo */
315: putgen (); /* initial generation message*/
316: }
317:
318:
319: /*** go - steps through generations until a key is pressed
320: *
321: * Calls step() until a key is hit, which terminates the loop
322: * and the key is left on the buffer for main() to process
323: * as a command.
324: * step()'s return is checked to see if this program has just
325: * moved from background to foreground, in which case the
326: * the screen will need manual updating with showingrid().
327: * If the D or U keys are hit, down() or up() is executed.
328: *
329: * Entry: none
330: *
331: * Exit: None
332: *
333: * Calls: step(), showingrid()
334: */
335: void
336: go () {
1.1.1.2 ! root 337: KBDKEYINFO kbd; /* return from KBD call */
! 338: int background=0; /* 0 if forground, 1 backgrd */
! 339: MOUEVENTINFO event; /* return from mouse read */
! 340: USHORT type=0; /* for no waits on mouse read*/
! 341: MOUQUEINFO num; /* return for GetNumQueEl */
! 342: unsigned x,y; /* for slowing down loops */
1.1 root 343:
344: /* step until key or button is hit, executing GoAble commands */
1.1.1.2 ! root 345: kbd.chScan=0;
! 346: event.fs=0;
! 347: while (!kbd.chScan) {
1.1 root 348: if (step ())
349: background=1;
350: else if (background==1) {
351: background=0;
352: showingrid();
353: }
354: if (Mouse) {
355: /* read all events on que */
1.1.1.2 ! root 356: MouGetNumQueEl (&num, Mouse);
! 357: while (num.cEvents--) {
! 358: MouReadEventQue (&event, &type, Mouse);
! 359: if (event.fs & (2|4|8|16)) { /* leave if mouse hit*/
1.1 root 360: wait4release();
361: return;
362: }
363: }
364: }
365:
366: /* check keys, no wait. Execute if GoAble command */
1.1.1.2 ! root 367: KbdPeek (&kbd, 0);
! 368: if (kbd.chScan) {
1.1 root 369: /* walk command structure and execute function, highlighting */
370: for(x=0; ComLine[x].Fun != 0; x++)
1.1.1.2 ! root 371: if ((kbd.chChar & 0xdf) == *(ComLine[x].Name)
1.1 root 372: && ComLine[x].GoAble) {
373: gputs(ComLine[x].Name,COM_ROW,ComLine[x].Col,0xff);
1.1.1.2 ! root 374: KbdCharIn (&kbd, 1, 0);
! 375: kbd.chScan=0;
1.1 root 376: (*(ComLine[x].Fun))();
377: if (ComLine[x].Fun) /*restore name if not removed*/
378: gputs(ComLine[x].Name,COM_ROW,ComLine[x].Col,0);
379: }
380: }
381: if (Slow) /* slow down if requested */
382: for (x=Slow; x--;)
383: for (y=50000; y--;);
384: }
385: }
386:
387:
388: /*** halt - does nothing
389: */
390: void
391: halt () {
392: int i;
393:
394: for (i=30000; i--;); /* short pause */
395: }
396:
397:
398: /*** quit - exit after prompting for certainty
399: *
400: * Entry: None
401: *
402: * Exit: None
403: */
404: void
405: quit () {
406: gputs (Logo, PROMPT_ROW, PROMPT_COL, 0);
407: gputs ("Are you sure?", PROMPT_ROW, PROMPT_COL, 0);
408: if (gethit () == 'Y')
409: exitlife (); /* if yes, do quit routine */
410: gputs (Logo, PROMPT_ROW, PROMPT_COL, 0); /* else, continue */
411: putgen(); /* put back gen count*/
412: }
413:
414:
415: /*** diskread - read new grid from disk
416: *
417: * getfilespec() is called to get a file name from the user which
418: * is put in Filespec.
419: * Then an internal grid is read from the disk which is in the form:
420: * WORD SIGNATURE ; Life file signature
421: * WORD rows
422: * WORD columns
423: * WORD generation
424: * (rows*columns/BYTEINCOLS) BYTES of the bit mapped grid
425: *
426: * InGrid is ReAlloced to the size of the newly read grid and
427: * InCol, InRow, and Generation are all set the values contained
428: * in the file read.
429: *
430: * Entry: InGrid points to internal grid defined by InCol and InRow
431: *
432: * Exit: InGrid is ReAlloced to size of newly read grid.
433: * InCol, InRow, and Generation are all set the values
434: * contained in the file read.
435: * d
436: * Calls: getfilespec ();
437: */
438: void
439: diskread () {
1.1.1.2 ! root 440: HFILE handle; /* file handle */
! 441: USHORT action; /* return for file calls */
1.1 root 442: unsigned signature; /* life file signature word */
443:
444: /* get file name into Filespec */
445: if (getfilespec ())
446: return; /* return if user hit ESC */
447:
448: /* open file, fail if it doesn't exist */
1.1.1.2 ! root 449: if (DosOpen ((PSZ) Filespec, &handle,
! 450: &action, 0L, 0, 0x01, 0x42, 0L)) {
1.1 root 451: anerror ("Can't open file", 0);
452: Filespec[0]=0; /* clear bad file name */
453: return;
454: }
455:
456: /* read appropriate from file */
1.1.1.2 ! root 457: if (DosRead (handle, (PSZ) &signature, sizeof (signature),
! 458: &action) || action != sizeof (signature))
1.1 root 459: anerror ("Error writing to file", 0);
460:
461: else if (signature != SIGNATURE) {
462: anerror ("Not a life file", 0);
1.1.1.2 ! root 463: if (DosClose (handle))
1.1 root 464: anerror ("Error closing file", 0);
465: return;
466: }
1.1.1.2 ! root 467: else if (DosRead (handle, (PSZ) &InRow, sizeof (InRow),
! 468: &action) || action != sizeof (InRow))
1.1 root 469: anerror ("Error reading from file", 0);
470:
1.1.1.2 ! root 471: else if (DosRead (handle, (PSZ) &InCol, sizeof (InCol),
! 472: &action) || action != sizeof (InCol))
1.1 root 473: anerror ("Error reading from file", 0);
474:
1.1.1.2 ! root 475: else if (DosRead (handle, (PSZ) &Generation,
! 476: sizeof (Generation), &action) ||
1.1 root 477: action != sizeof (Generation))
478: anerror ("Error reading from file", 0);
479:
480: /* change size of *InGrid to match saved pattern */
1.1.1.2 ! root 481: else if (DosReallocSeg (InRow*InCol/BYTEINCOLS, (unsigned)
1.1 root 482: ((long) InGrid/0x10000L)))
483: anerror ("Error allocating memory", 0);
484:
1.1.1.2 ! root 485: else if (DosRead (handle, InGrid, InRow*InCol/BYTEINCOLS,
! 486: &action) || action != InRow*InCol/BYTEINCOLS)
1.1 root 487: anerror ("Error reading from file", 0);
488:
489: /* show the newly loaded pattern on the screen */
490: showingrid ();
491:
492: /* close file */
1.1.1.2 ! root 493: if (DosClose (handle))
1.1 root 494: anerror ("Error closing file", 0);
495: }
496:
497:
498: /*** step - advance one generation on screen and internally
499: *
500: * Uses dostep to advance the current internal and screen grid
501: * to the next generation. dostep() returns 1 if the screen
502: * was not available and thus no update was made, this allows
503: * the program to continue to execute in the background.
504: * The return from dostep() is passed back to step()'s caller.
505: *
506: * Entry: None
507: *
508: * Exit: Returns 1 if executing in background and there was thus
509: * no screen update.
510: * Else, returns 0 if in forground
511: * Generation is incremented.
512: *
513: * Calls: putgen(), dostep [assembler routine]
514: */
515: int
516: step() {
517: int rc; /* return code from dostep */
518:
519: /* do the stepping using an assembler routine for speed */
520: rc=dostep(InGrid, InGrid2, InRow, InCol); /* do the step */
521:
522: Generation++; /* advance the count */
523: putgen(); /* and display gen */
524: return(rc);
525: }
526:
527:
528: /*** diskwrite - write internal grid to disk
529: *
530: * getfilespec() is called to get a file name from the user which
531: * is put in Filespec.
532: * Then the internal grid is saved to disk in the form:
533: * WORD SIGNATURE ; Life file signature
534: * WORD rows
535: * WORD columns
536: * WORD generation
537: * (rows*columns/BYTEINCOLS) BYTES of the bit mapped grid
538: *
539: * Entry: InGrid points to internal grid defined by InCol and InRow
540: *
541: * Exit: None
542: *
543: * Calls: getfilespec ();
544: */
545: void
546: diskwrite () {
1.1.1.2 ! root 547: HFILE handle; /* file handle */
! 548: USHORT action; /* return for file calls */
1.1 root 549: unsigned signature=SIGNATURE; /* life file signature word */
550:
551: /* get file name into Filespec */
552: if (getfilespec ())
553: return; /* return if user hit ESC */
554:
555: /* open file and truncate or create it if it doesn't exist */
1.1.1.2 ! root 556: if (DosOpen ((PSZ) Filespec, &handle,
! 557: &action, 0L, 0, 0x12, 0x42, 0L)) {
1.1 root 558: anerror ("Can't open file", 0);
559: Filespec[0]=0; /* clear bad file name */
560: return;
561: }
562:
563: /* write appropriate info to file */
1.1.1.2 ! root 564: if (DosWrite (handle, (PSZ) &signature, sizeof (signature),
! 565: &action) || action != sizeof (signature))
1.1 root 566: anerror ("Error writing to file", 0);
567:
1.1.1.2 ! root 568: else if (DosWrite (handle, (PSZ) &InRow, sizeof (InRow),
! 569: &action) || action != sizeof (InRow))
1.1 root 570: anerror ("Error writing to file", 0);
571:
1.1.1.2 ! root 572: else if (DosWrite (handle, (PSZ) &InCol, sizeof (InCol),
! 573: &action) || action != sizeof (InCol))
1.1 root 574: anerror ("Error writing to file", 0);
575:
1.1.1.2 ! root 576: else if (DosWrite (handle, (PSZ) &Generation,
! 577: sizeof (Generation), &action) ||
1.1 root 578: action != sizeof (Generation))
579: anerror ("Error writing to file", 0);
580:
1.1.1.2 ! root 581: else if (DosWrite (handle, InGrid, InRow*InCol/BYTEINCOLS,
! 582: &action) || action != InRow*InCol/BYTEINCOLS)
1.1 root 583: anerror ("Error writing to file", 0);
584:
585: /* close file */
1.1.1.2 ! root 586: if (DosClose (handle))
1.1 root 587: anerror ("Error closing file", 0);
588: }
589:
590:
591: /*** up - speeds up GOs (decrement Slow)
592: */
593: void
594: up() {
595: int i;
596:
597: if (Slow)
598: Slow--;
599: if (!Slow) {
600: /* if at top speed take out message */
601: for(i=0; *(ComLine[i].Name) != 'U'; i++);
602: ComLine[i].Fun=0;
603: gputs (" ", COM_ROW, ComLine[i].Col, 0);
604: }
605: for (i=30000; i--;); /* short pause */
606: }
607:
608:
609: /*** down - slows down GOs (increment Slow)
610: */
611: void
612: down() {
613: int i;
614:
615: Slow++;
616: for (i=30000; i--;); /* short pause */
617: if (Slow){ /* if at top speed, display UP */
618: for(i=0; *(ComLine[i].Name) != 'U'; i++);
619: ComLine[i].Fun=up;
620: for (i=0; ComLine[i].Fun != 0; i++) /* print command line */
621: gputs (ComLine[i].Name, COM_ROW, ComLine[i].Col, 0);
622: }
623: }
624:
625:
626: /*** beep - beep the speaker for bad commands
627: */
628: void
629: beep () {
1.1.1.2 ! root 630: DosBeep (500, 50);
1.1 root 631: }
632:
633:
634: /*** showingrid - display internal grid on screen
635: *
636: * Clears the screen using the draw_grid routine and then
637: * puts the internal grid on the screen using fill() so as to
638: * not be resolution dependent.
639: *
640: * Entry: InGrid points to internal grid defined by InCol and InRow.
641: * ScrSeg points to the screen buffer.
642: * ScrRow and ScrCol describe the dimensions of the screen grid.
643: * Logo points to program logo that also clears the prompt line
644: *
645: * Exit: None
646: *
647: * Calls: fill(), putgen(), gputs(), draw_grid [assembler routine]
648: */
649: void
650: showingrid () {
651: int x, y; /* cell coordinates for loop */
1.1.1.2 ! root 652: BYTE retcode; /* for VIOSCRLOCK */
1.1 root 653:
654: /* prepare blank screen so we only have to fill in cells that are on */
655: draw_grid (); /* draw blank grid on screen */
656: /* print command line */
657: for (x=0; ComLine[x].Fun != 0; x++)
658: gputs (ComLine[x].Name, COM_ROW, ComLine[x].Col, 0);
659: gputs (Logo, PROMPT_ROW, PROMPT_COL, 0);
660: putgen ();
661:
662: /* loop through the screen sized area of the grid */
1.1.1.2 ! root 663: VioScrLock (1, &retcode, 0); /* get screen access */
1.1 root 664: for (y=ScrRow; y--;)
665: for (x=ScrCol; x--;)
666: /* if the internal cell is on, turn on the screen one*/
667: if (*(InGrid + (x + y*InCol)/BYTEINCOLS) & (0x80 >> (x&7)))
668: fill (x, y);
1.1.1.2 ! root 669: VioScrUnLock (0);
1.1 root 670: }
671:
672:
673: /*** getfilespec - get a file name from the user
674: *
675: * Prompts user for a file name for use with diskread() or diskwrite().
676: * The last file name used is shown on the screen as a default
677: * and the user can hit enter (or left mouse button) to specify the
678: * default name. If any other key is pressed, this routine takes the
679: * input until an enter and returns in Filespec. If ESC is hit any time
680: * during typeing, the input will be aborted and the buffer cleared.
681: *
682: * Entry: Filespec points to default file name
683: * Logo points to program logo that also clears the prompt line
684: *
685: * Exit: Filespec points to new file name
686: * returns 0x1b if ESC was hit during entry, else 0
687: *
688: * Calls: gputs(), putgen()
689: */
690: int
691: getfilespec () {
1.1.1.2 ! root 692: int c=0; /* index for Filespec[] */
! 693: KBDKEYINFO kbd; /* return for KBD call */
! 694: unsigned space=0x0020; /* space character for print */
! 695: MOUEVENTINFO event; /* return for mouse */
! 696: USHORT type=0; /* mouse reads w/no wait */
1.1 root 697:
1.1.1.2 ! root 698: kbd.chScan=0;
! 699: event.fs=0;
1.1 root 700: /* put up default file name if one exists */
701: if (Filespec[0]) {
702: gputs ("Type file name or enter for default: ", PROMPT_ROW,
703: PROMPT_COL,0);
704: gputs (Filespec, PROMPT_ROW, FILE_COL, 0);
705: /* wait for first key or button to see if they accept default */
1.1.1.2 ! root 706: while (!kbd.chScan && !(event.fs & (2|4|8|16))) {
1.1 root 707: if (Mouse)
1.1.1.2 ! root 708: MouReadEventQue (&event, &type, Mouse);
! 709: KbdPeek (&kbd, 0);
1.1 root 710: }
711: }
1.1.1.2 ! root 712: if (event.fs & (8 | 16)) { /* if right button hit, same as ESC */
! 713: kbd.chChar = 0x1b;
1.1 root 714: wait4release();
715: }
1.1.1.2 ! root 716: if (event.fs & (2 | 4)) { /* if left button hit, same as \r */
1.1 root 717: wait4release();
718: gputs (Logo, PROMPT_ROW, PROMPT_COL, 0);
719: putgen();
720: return (0);
721: }
1.1.1.2 ! root 722: if (kbd.chChar == 0x1b) { /* if ESC, return */
1.1 root 723: gputs (Logo, PROMPT_ROW, PROMPT_COL, 0);
724: putgen();
725: return (0x1b); /* return escape */
726: }
727:
728: gputs(Logo, PROMPT_ROW, PROMPT_COL, 0);
729: gputs("Type file name, followed by enter: ",PROMPT_ROW,PROMPT_COL,0);
730:
731: /* read chars and print them until \r */
1.1.1.2 ! root 732: while (!(KbdCharIn (&kbd, 0, 0)) &&
! 733: kbd.chChar != '\r') {
! 734: if (kbd.chChar == '\b') { /* if backspace, backspace */
1.1 root 735: if (c > 0) { /* prevent underflow*/
736: Filespec[--c]=0;
737: /* erase backed-out character*/
738: gputs (&space, PROMPT_ROW, FILE_COL+c,0);
739: }
740: else /* if overflow, yell */
741: beep();
742: }
1.1.1.2 ! root 743: else if (kbd.chChar == 0x1b) { /* if escape was hit */
1.1 root 744: Filespec[0]=0; /* blank name */
745: gputs (Logo, PROMPT_ROW, PROMPT_COL, 0);
746: putgen();
747: return (0x1b); /* return escape */
748: }
1.1.1.2 ! root 749: else if (kbd.chChar <46 || kbd.chChar > 122)
1.1 root 750: beep(); /* if not char, beep */
751: else
752: if (c < sizeof (Filespec)) { /* prevent overflow */
1.1.1.2 ! root 753: Filespec[c]=kbd.chChar; /* add to string */
1.1 root 754: Filespec[++c]=0; /* null terminal */
755: }
756: else /* if overflow, yell */
757: beep();
758:
759: gputs (Filespec, PROMPT_ROW, FILE_COL, 0); /* print new name */
760: }
761: /* return generation count to screen */
762: gputs (Logo, PROMPT_ROW, PROMPT_COL, 0);
763: putgen();
764: return (0);
765: }
766:
767:
768: /*** putgen - puts the current generation counter on prompt line
769: *
770: * Entry: Generation = current generation number
771: *
772: * Exit: None
773: *
774: * Calls: gputs()
775: */
776: void
777: putgen ()
778: {
779: char num[8]; /* number string to print */
780: int i, g;
781:
782: gputs ("Generation: ", PROMPT_ROW, PROMPT_COL,0);/* gen label*/
783:
784: if (Generation == 0)
785: gputs ("0", PROMPT_ROW,GEN_COL,0);/*if special case need this*/
786: else { /* not 0, do this: */
787: for (g=Generation, i=sizeof(num)-1; i-- >= 0 && g; g/=10)
788: num[i]=(g%10)+'0'; /* convert int to string */
789: num[sizeof(num)-1]= 0; /* zero terminated for gputs */
790: gputs (&num[i+1], PROMPT_ROW, GEN_COL,0); /* print number */
791: }
792: }
793:
794:
795: /*** fill - fill in a grid cell on the screen and internally
796: *
797: * fill (x, y)
798: *
799: * Entry: x = grid horizontal coordinate
800: * y = grid vertical coordinate
801: * ScrSeg = screen buffer segment
802: * VioScrLock is active
803: * InGrid points to internal grid defined by InCol and InRow
804: * ScrRow and ScrCol define limits of screen grid
805: * Currently, must use 79x25 grid drawn by draw_grid()
806: * Currently, screen must be in 640x200 b/w mode
807: *
808: * Exit: None
809: */
810: void
811: fill (x, y)
812: int x,y;
813: {
814: char far *gridptr;
815:
816: /* only do if within screen grid limits */
817: if (x < ScrCol && y < ScrRow) {
818: /* set gridptr to & in screen buff of cell to fill */
819: (long) gridptr = ScrSeg*0x10000; /* fill in address */
820: (unsigned) gridptr = (x*SizeScrCol/BIT)+(y*SizeScrRow/Cga*SCR_WID);
821: /* fill in screen cell */
822: *(gridptr+OddPage)=0x7f;
823: *(gridptr+SCR_WID)=0x7f;
824: *(gridptr+OddPage+SCR_WID)=0x7f;
825: }
826: /* fill in internal cell*/
827: /*InGrid= &internal grid of x,y*/
828: gridptr = InGrid + x/BYTEINCOLS + y*InCol/BYTEINCOLS;
829: *gridptr = *gridptr | (0x80 >> (x & 7)); /* fill bit */
830: }
831:
832:
833: /*** remove - clear a grid cell on the 79x45 grid screen and internally
834: *
835: * remove (x, y)
836: *
837: * Entry: x = grid horizontal coordinate
838: * y = grid vertical coordinate
839: * ScrSeg = screen buffer segment
840: * VioScrLock is active
841: * InGrid points to internal grid defined by InCol and InRow
842: * ScrRow and ScrCol define limits of screen grid
843: * Currently, must use 79x25 grid drawn by draw_grid()
844: * Currently, screen must be in 640x200 b/w mode
845: *
846: * Exit: None
847: */
848: void
849: remove (x, y)
850: int x,y;
851: {
852: char far *gridptr;
853:
854: /* only do if within screen grid limits */
855: if (x < ScrCol && y < ScrRow) {
856: /* set gridptr to & in screen buff of cell to fill */
857: (long) gridptr = ScrSeg*0x10000; /* fill in address */
858: (unsigned) gridptr = (x*SizeScrCol/BIT)+(y*SizeScrRow/Cga*SCR_WID);
859: /* fill in screen cell */
860: *(gridptr+OddPage)=0x80;
861: *(gridptr+SCR_WID)=0x80;
862: *(gridptr+OddPage+SCR_WID)=0x80;
863: }
864:
865: /* remove internal cell*/
866: /*InGrid= &internal grid of x,y*/
867: gridptr = InGrid + x/BYTEINCOLS + y*InCol/BYTEINCOLS;
868: *gridptr = *gridptr & ~((0x80 >> (x & 7))); /* remove bit */
869: }
870:
871:
872: /*** kbdreadeventque - simulates MouReadEventQue with the keyboard
873: *
874: * F9 key is responded to as left button, F10 key as right.
875: * Pointer location is advanced by SizeScrCol or SizeScrRow according
876: * to direction of direction key hits.
1.1.1.2 ! root 877: * Does not wait for input, instead returns 0 event fs.
1.1 root 878: * Does not check MouDevStatus, instead always returns pixel coordinates
879: * Event buffer is filled according to MouReadEventQue
880: *
881: * Entry: event - structure to return data
882: * ptrrow - current point row
883: * ptrcol - current pointer column
884: *
885: * Exit: None
886: * *event contains any event that may have occurred
887: */
888: int
889: kbdreadeventque (event, ptrrow, ptrcol)
1.1.1.2 ! root 890: MOUEVENTINFO *event;
1.1 root 891: int ptrrow, ptrcol;
892: {
1.1.1.2 ! root 893: KBDKEYINFO kbd; /* return for KBD call */
1.1 root 894:
1.1.1.2 ! root 895: kbd.chChar=0; /* clear data struct residue*/
! 896: kbd.chScan=0;
1.1 root 897: /* peek at key buffer */
1.1.1.2 ! root 898: KbdPeek (&kbd, 0);
1.1 root 899:
900: /* if it is an extended key code, process it */
1.1.1.2 ! root 901: if (kbd.chChar == 0 && kbd.chScan) {
! 902: KbdCharIn (&kbd, 1, 0); /* take char */
! 903: event->row=ptrrow; /* initial position */
! 904: event->col=ptrcol;
! 905: switch (kbd.chScan) { /* check second byte of code */
1.1 root 906: case 0x48: /* cursor up */
1.1.1.2 ! root 907: event->row -= SizeScrRow;
! 908: event->fs=1;
1.1 root 909: break;
910: case 0x50: /* cursor down */
1.1.1.2 ! root 911: event->row += SizeScrRow;
! 912: event->fs=1;
1.1 root 913: break;
914: case 0x4b: /* cursor left */
1.1.1.2 ! root 915: event->col -= SizeScrCol;
! 916: event->fs=1;
1.1 root 917: break;
918: case 0x4d: /* cursor right */
1.1.1.2 ! root 919: event->col += SizeScrCol;
! 920: event->fs=1;
1.1 root 921: break;
922: case 0x43: /* left button (F9) */
1.1.1.2 ! root 923: event->fs=4;
1.1 root 924: break;
925: case 0x52: /* left button (INS) */
1.1.1.2 ! root 926: event->fs=4;
1.1 root 927: break;
928: case 0x44: /* right button (F10) */
1.1.1.2 ! root 929: event->fs=16;
1.1 root 930: break;
931: case 0x53: /* right button (DEL) */
1.1.1.2 ! root 932: event->fs=16;
1.1 root 933: break;
934: default: /* anything else is no good */
1.1.1.2 ! root 935: event->fs=0;
1.1 root 936: break;
937: }
938: }
939: else
1.1.1.2 ! root 940: event->fs=0; /* if no extended key */
1.1 root 941: }
942:
943:
944: /*** xorptr - xors the mouse ptr on the screen
945: *
946: * xorptr (ptrrow, ptrcol)
947: *
948: * Entry: ptrrow = pixel row on screen
949: * ptrcol = pixel column on screen
950: * ScrSeg = screen buffer segment
951: *
952: * Exit: None
953: */
954: void
955: xorptr (ptrrow, ptrcol)
956: unsigned ptrrow, ptrcol;
957: {
958: char far *gridptr;
1.1.1.2 ! root 959: unsigned mask=0; /* bit mask for pointer shape*/
! 960: int x=8; /* count for rows of pointer */
! 961: BYTE retcode; /* return from VioScrLock */
1.1 root 962:
963: /* set gridptr to & in screen buff to xor */
964: ptrrow++; /* 1st move ptr to next row*/
965: (long) gridptr = ScrSeg*0x10000; /* fill in segment address */
966: (unsigned) gridptr = ptrcol/x+(ptrrow/Cga*SCR_WID);/* fill offset*/
967:
968: /* fill in screen pointer */
1.1.1.2 ! root 969: VioScrLock (1, &retcode, 0); /* get screen */
1.1 root 970: while (x--) {
971: mask = mask >> 1; /* make mask thicker */
972: mask |= 0x8000; /* at bottom */
973: if (ptrrow & 1 && Cga == 2) /* if odd bit plane */
974: gridptr += OddPage;
975: /* xor mask to scr, being sure to reverse bytes in word */
976: *(gridptr+1) ^= (char) (mask >> (ptrcol & 7));
977: *(gridptr) ^= (char) ((mask >> (ptrcol & 7)) / 256);
978: if (ptrrow & 1 && Cga == 2) /* if odd bit plane */
979: gridptr -= OddPage; /* plane, next row*/
980: ptrrow++; /* next row */
981: if (!(ptrrow & 1) && Cga == 2) /*only advance on odd*/
982: gridptr += SCR_WID; /* row w/o CGA */
983: }
1.1.1.2 ! root 984: VioScrUnLock (0); /* give back screen */
1.1 root 985: }
986:
987:
988: /*** gputs - put a character string on the graphics screen
989: *
990: * gputs (string, row, col)
991: *
992: * Draws the passed asciiz string on the screen unless the screen
993: * is not available (i.e., if running in background screen group)
994: * in which case this routine does nothing so as to not hold up
995: * the program.
996: *
997: * Entry: string - address of asciiz string to print
998: * row - alpha row to print at
999: * col - alpha col to print at
1000: * mask - xor'ed with bytes of character as printed
1001: *
1002: * Exit: None
1003: *
1004: * Calls: gputchar()
1005: *
1006: * Warning: Prints graphics characters for ascii control codes (<32).
1007: * Does not wrap-around at line end.
1008: */
1009: void
1010: gputs (string, row, col, mask)
1011: char *string;
1012: unsigned col, row;
1013: char mask;
1014: {
1.1.1.2 ! root 1015: static VIOFONTINFO fontdata = {14,1,FONT_ROW,8,0L,0};/*for GetFont*/
1.1 root 1016: char c;
1.1.1.2 ! root 1017: BYTE retcode; /* for VioScrLock */
1.1 root 1018: char far *scr_addr; /* address in screen buffer
1019: to print at */
1020:
1021: /* try lock without waiting for availability */
1.1.1.2 ! root 1022: VioScrLock (0, &retcode, 0); /* get screen access */
1.1 root 1023:
1024: /* do display only if screen available now */
1025: if (retcode == 0) {
1026: /* get address of font table if haven't yet */
1.1.1.2 ! root 1027: if (!fontdata.pbData)
! 1028: VioGetFont (&fontdata, 0);
1.1 root 1029:
1030: (long) scr_addr = ScrSeg*0x10000; /* fill in segment address*/
1031: (unsigned)scr_addr = col+(row*SCR_WID*(FONT_ROW/Cga));/*offset*/
1032: while ((c=*(string++)) != 0) /* print each char */
1.1.1.2 ! root 1033: gputchar(scr_addr++,
! 1034: (ULONG)fontdata.pbData+c*FONT_ROW,mask);
! 1035: VioScrUnLock (0); /* give up screen */
1.1 root 1036: }
1037: }
1038:
1039:
1040: /*** gputchar - put a character on the graphics screen
1041: *
1042: * gputchar (scr_addr, font_addr, mask)
1043: *
1044: * Entry: scr_addr - far address in screen buffer to place character
1045: * must be on even Cga bit plane
1046: * font_addr - far address in font table bit pattern
1047: * mask - xor'ed with each byte put on screen
1.1.1.2 ! root 1048: * VioScrLock is active
1.1 root 1049: *
1050: * Exit: None
1051: */
1052: void
1053: gputchar(scr_addr, font_addr, mask)
1054: char far *scr_addr;
1055: char far *font_addr;
1056: char mask;
1057: {
1058: int i=FONT_ROW; /* count to draw whole character */
1059:
1060: while (i--) {
1061: if (i & 1 && Cga == 2) /* if odd bit plane */
1062: scr_addr += OddPage-SCR_WID;
1063: *scr_addr = (*(font_addr++) ^ mask);/* put byte on even plane*/
1064: if (i & 1 && Cga == 2) /* if odd bit plane */
1065: scr_addr -= OddPage;
1066: scr_addr+=SCR_WID; /* move down a line */
1067: }
1068: }
1069:
1070:
1071: /*** anerror - error handler
1072: *
1073: * anerror (message, fatal);
1074: *
1075: * Prints passed error message and waits for a key hit to allow
1076: * user to read it. If fatal flag is 0, the routine then returns,
1077: * otherwise the routine restores the original screen mode, clears
1078: * the screen, and exits.
1079: *
1080: * Entry: message - string to print describing error
1081: * fatal - flag, if == 0, this procedure prompts for any key hit
1082: * then returns. If != 0, error is fatal & program exits.
1083: * Logo points to program logo that also clears the prompt line
1084: *
1085: * Exit: None
1086: *
1087: * Calls: gputs(), putgen()
1088: */
1089: void
1090: anerror (message, fatal)
1091: char message[];
1092: int fatal;
1093: {
1.1.1.2 ! root 1094: KBDKEYINFO kbd; /* return for KBD call */
1.1 root 1095:
1096: gputs (Logo, PROMPT_ROW, PROMPT_COL,0);
1097: gputs (message, PROMPT_ROW, PROMPT_COL, 0);/* print passed message */
1098:
1099: if (fatal) {
1100: gputs(": Press any key to exit",PROMPT_ROW,strlen(message),0);
1101: gethit ();
1102: exitlife();
1103: }
1104: else {
1105: gputs (": Press any key to continue",PROMPT_ROW,
1106: strlen(message), 0);
1107: gethit ();
1108: gputs (Logo, PROMPT_ROW, PROMPT_COL, 0);
1109: putgen();
1110: }
1111: }
1112:
1113:
1114: /*** gethit - waits for a key hit or mouse button hit
1115: *
1116: * Entry: None
1117: *
1118: * Exit: Returns character hit (forced upper case) or 'Y' for left
1119: * mouse button and 'N' for right mouse
1120: */
1121: char
1122: gethit () {
1.1.1.2 ! root 1123: KBDKEYINFO kbd; /* for keyboard */
! 1124: MOUEVENTINFO event; /* for mouse */
! 1125: USHORT type=0;
! 1126: char rc; /* return code */
1.1 root 1127:
1128: /* loop till a hit */
1.1.1.2 ! root 1129: kbd.chScan=0;
! 1130: event.fs=0;
! 1131: while (!kbd.chScan && !(event.fs & (2|4|8|16))) {
1.1 root 1132: if (Mouse)
1.1.1.2 ! root 1133: MouReadEventQue (&event, &type, Mouse);
! 1134: KbdCharIn (&kbd, 1, 0);
! 1135: }
! 1136: if (kbd.chScan) /* if key, return char */
! 1137: rc=kbd.chChar & 0xdf;
! 1138: else if (event.fs & (8 | 16)) { /* if right */
1.1 root 1139: rc='N';
1140: wait4release ();
1141: }
1.1.1.2 ! root 1142: else if (event.fs & (2 | 4)) { /* if left down*/
1.1 root 1143: rc='Y';
1144: wait4release ();
1145: }
1146: return (rc);
1147: }
1148:
1149:
1150: /*** wait4release - wait until mouse buttons are up before returning
1151: *
1152: * Entry: Mouse = mouse handle or 0 if no mouse
1153: */
1154: void
1155: wait4release () {
1.1.1.2 ! root 1156: MOUEVENTINFO event;
! 1157: USHORT type = 0; /* if OS/2, wait for events */
1.1 root 1158:
1.1.1.2 ! root 1159: event.fs=2; /* force 1st read */
1.1 root 1160: if (Mouse) /* loop till no button events */
1.1.1.2 ! root 1161: while ((event.fs & (2|4|8|16)) || event.Time==0)
! 1162: MouReadEventQue (&event, &type, Mouse);
! 1163:
1.1 root 1164: }
1165:
1166:
1167: /*** exitlife - exit program, resetting original screen mode
1168: *
1169: * Called by a Ctrl-C
1170: *
1171: * Entry: Savemode contains original screen mode info
1172: *
1173: * Exit: None
1174: */
1175: void far pascal
1176: exitlife() {
1.1.1.2 ! root 1177: VioSetMode (&Savemode, 0); /* restore*/
! 1178: VioScrollUp (0,0,-1,-1,-1, (char far *) Cell,0); /* cls */
! 1179: DosExit (EXIT_PROCESS, 0); /* exit all threads */
1.1 root 1180: }
1181:
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.