Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Tools

Structured Programming


MAY89: STRUCTURED PROGRAMMING

STRUCTURED PROGRAMMING

Pizza Terra

Jeff Duntemann, K16RA

One roasty Saturday last summer, Mr. Byte and I were steaming over The Hill in the Magic Van to pick up some end mills in San Jose when the KWSS DJ caught my ear:

"... and up next is the great new single by Pizza Terra!"

Pizza Terra? What a great name for a rock band! We haven't seen names like that since the Strawberry Alarm Clock and the Peppermint Trolley Company, and I was duly impressed --if also wondering why I hadn't ever heard of Pizza Terra before.

Then he played the song. And moment by moment I found myself thinking, Boy, that sure sounds like Peter Citera ...

Peter Citera ... Pete Citera ... Pizza Terra!

I'd been type cast!

Homonymously Yours

The last part of Pascal to resist my understanding was the subject of type casting, and in Pizza Terra I've found a wonderful metaphor to make it go down easier. What the DJ said was perfectly correct: Four spoken syllables that sounded like "pee-tzuh-tear-uh." Given your typical DJ's level of enunciation, those four syllables could as easily be taken to mean "Pizza Terra" as "Pete Citera."

And that's the whole point.

Type casting is the process of taking a variable of one type and treating it as a variable of another type. This sticks to the roofs of most mouths fed on Pascal and Modula-2, both of which are pretty hardnosed about type checking. Type casting jumps the type checking safety railing, allowing you to get into two different kinds of trouble if you're not careful.

One kind of trouble is semantic nonsense. Both Turbo Pascal and Modula-2 allow a variable of type Char to be cast onto a variable of type Boolean through this notation:

  MyChar:= CHAR(MyBoolean);

This is completely legal. But what does it mean? Can a character be True? Is False an aspect of the letter 'Q'? It sounds like nonsense to me --which is a major reason we have type checking in civilized languages like Pascal and Modula-2.

The Pascal/Modula-2 statement above implies that some sort of conversion process is going on, and that processor cycles are being spent somehow converting data from type Boolean to type Char before copying them to variable MyChar. Not so --the data is simply copied from MyBoolean to MyCharwith nary a thought for data typing. It's like picking up a cylindrical crucible of molten aluminum and pouring the metal into a cubical mold. The shape of the aluminum changes, but the metal itself is still aluminum. It doesn't change to copper or zinc.

Underneath the level of Pascal or Modula-2, a Boolean variable is just a single byte of memory with some bit pattern in it. Like the syllables of speech, what a byte of memory means to us is pretty much what we decide it is to mean, according to some preagreed scheme. If a byte of memory with the value 01H is poured into a mold marked Boolean, we agree that it has the value True, if we pour the same byte into a mold marked Char, it comes up on the screen as a white smiley face.

Think of type casting as another form of metal casting: pouring raw materials into a mold. The mold dictates the shape of the metal, but the underlying nature of the raw material is unchanged in the process. Cast the four syllables "pee-tzuh-tear-uh" into a mold marked "male vocalist" and you get Pete Citera. Cast the same four syllables into a mold marked "imaginary Sixties rock band" and you get Pizza Terra.

The Second Kind of Trouble

It's easy enough to imagine a second kind of trouble connected with breaking the rules of type checking: What happens if you have more data in one type than in another? If you cast 6 bytes of data into 10 bytes of data, what do you have? Or worse, if you cast 10 bytes of data into 6 bytes of data, does anything overflow? Do you overwrite unrelated variables that just happen to be adjacent in memory?

Casting between any two types, even of different sizes, is possible in both Turbo Pascal and Modula-2 by the same syntax shown earlier. If the source type is smaller than the destination type, you end up with a partially filled destination type. The generated code simply picks up bytes from the source type, starting with the lowest memory address, and drops bytes into the destination type until there are no more bytes to drop. Coping with the resulting semantic nonsense is left to you, but there's no danger of overwriting adjacent data.

When there's more source than destination, the code copies bytes from the source until the destination is full, then stops. This eliminates any danger of overwriting data adjacent in memory.

Sign Extension and Modula-2 Numeric Casts

One hiccup in the casting process occurs when data from one numeric type is moved into another. The notion of simply moving bytes blindly from one variable to another no longer applies.

Turbo Pascal and Modula-2 perform something called sign extension when data is moved between a signed numeric type into a larger signed numeric type; for example, from type Integer to type LongInt. The sign bit of the shorter type is moved into the sign bit of the larger type, even though the bytes from the shorter type do not entirely fill the larger type. The end result is that a negative integer value cast onto a long integer will result in a negative long integer value.

Most Turbo Pascal users take sign extension for granted, because numeric types are nearly always assignment compatible, and explicit casting between numeric types never needs to be done. However, even if you explicitly cast an integer onto a long integer, sign extension will still happen.

Modula-2 is, again, much more stubborn than Pascal when moving numeric values between types. Nearly all movement of values between numeric types must be done through explicit casting. INTEGER and CARDINAL are assignment compatible, as are SHORTCARD and SHORTINT. But that's about it. All other numeric value transfers are done through an implied call to the VAL function, which includes sign extension.

Numeric types demonstrate one Pascal safety railing that can't be jumped: Turbo Pascal somewhat remarkably forbids a type cast of any real number type onto any integer type, just as it forbids assignment of any real number type to any integer type. Type Real cannot, in fact, be cast onto any other type. To get at the bytes inside type Real, you have to use union labor, as I'll explain shortly. Modula-2, by contrast, allows byte-by-byte casting of real numbers onto any non-numeric type at all.

An All Union Cast

One problem with type casting in Turbo Pascal is that it's not really part of the Pascal language, and other compilers in other environments may or may not implement it. For most of us that doesn't matter; I consider the differences in paradigms among environments (say, DOS, Mac, OS/2, and Unix) far more of a barrier to portability than any deviation from an HLL's syntactic standard.

But the issue remains for those who care: There is really only one portable way to perform typecasts in Pascal. This is the free union variant record (or simply the free union), and it is about the most peculiar thing Niklaus Wirth decided to include in his definition of Pascal.

The best way to approach free unions is to start with their less libertarian relatives, discriminated unions. (We usually call discriminated unions variant records.) The familiar Pascal variant record looks like this:

  ID =
       RECORD
   CASE Alien: Boolean OF
       True : (HomeStarID : StarID);
       False : (EarthID : CountryID);
  END;

There are three fields defined here (Alien, HomeStarID, and EarthID) but only two exist at any one time. The Boolean field Alien is called the tag field and is always present in any instance of the record type. Which of the other two fields is considered present in the record depends on the value loaded into Alien. If Alien contains True, then the other field in the record is HomeStarID. If Alien contains False, then the other field is EarthID.

Beginners often ask: Well, who enforces that? Nobody; there's nothing to enforce. People who think "enforcement" are thinking backwards. The tag field exists to tell users of the record what information was written into the record earlier so that those users can know how to interpret what they find. The tag field is a flag, not a switch. Changing the value of Alien has no effect on the data in the rest of the record. Given an instance of ID named ScienceOfficer, the tag field is used this way:

  WITH ScienceOfficer DO
       IF Alien THEN
             SendHyperwaveTo
             (HomeStarID)
       ELSE
             SendTelegramTo(EarthID);

Changing the value in Alien in violation of the agreed upon scheme (that is, setting it to False for that guy Spock) will cause confusion, but only semantic confusion. The system won't crash.

If you're sharp it may occur to you that variant records are themselves a means of type casting, and you're right. Once you write a HomeStarID into an ID record, you can ignore the tag field and read the identical data right back as an EarthID type, essentially casting a HomeStarID type onto an EarthID type.

And if the only reason for the record is to cast one type onto another, who needs the tag field? In fact, dropping the tag field is how we turn a discriminated union into a free union:

  ScreenAtom =
       RECORD
             CASE Boolean OF
             True : (Ch : Char; Attr : Byte);
             False : (Atom : Word);
       END;

Like the Cheshire cat, the tag field has vanished, but its type remains. The need for the tag field as flag is gone, but we need something to enumerate the different variants, and the lonely type specifier Boolean does that quite well by providing the True and False CASE labels. Any ordinal type (Char, Integer, Word, Byte, or enumerations) would do as well, and you don't have to account for every value in the type as long as you define at least two.

So what do we have here? A record of type ScreenAtom occupies 2 bytes of storage, and that's all. The two variants may be considered two molds into which those 2 bytes of storage may be cast. One mold provides two byte-sized compartments called Ch and Attr. The other is a single 2-byte field called Atom. We can write a word-sized value to the Atom field and then pick out its two component bytes separately as Ch or Attr. The reverse, of course, is just as true: We can build an atom in two operations, first by storing a character in Ch, and then by storing its attribute in Attr. That done, we can read the finished atom out as Atom:

  MyAtom.Ch := 'A';
  MyAtom.Attr := $07;
  MEMW[$B800 : 0]:= MyAtom.Atom;

If you haven't already recognized this record type, it's from the SCREENS.PAS unit presented in last month's column. A character on the PC's text screen is stored as 2 bytes side by side: The ASCII code for the character, and a byte specifying the attribute of the character; that is, its color for color screens, or things like underlining and reverse video for monochrome screens. ScreenAtom lets us look at these two bytes as a unit, or else as the two individual components, as we choose.

In the case of ScreenAtom, both of the two variants are of the same size, so everything's tidy. That's not a requirement; if the several variants vary in size, the record as a whole is as large as its largest variant. This prevents any danger to adjacent data.

Registers on a Half Shell

The best example of a free union whose variants are not the same size is the Registers type exported by the Turbo Pascal DOS unit, and the similar Registerstype exported by TopSpeed Modula-2's SYSTEM module. In both cases, the idea is to provide access to the 86-family CPU registers. Listing One (UNIONS.SRC) shows the two free unions side by side for comparison purposes.

There are ten accessible registers altogether: AX, BX, CX, DX, BP, SI, DI, DS, ES, and Flags. The first four, however, are commonly treated in two ways: As 16-bit units whose names end in X, or with each 16-bit unit seen as two 8-bit components ending in H (for high) and L (for low.) For example, AX is composed of AL and AH, BX of BL and BH, and so on.

Both free unions provide easy access to the four general-purpose registers AX, BX, CX, and DX as both 16-bit units and pairs of 8-bit halves. Given an instance of type Registers named Regs, when you want to access all of AX, you specify Regs.AX. If you simply want to access the high half of AX without disturbing the low half (say, for passing parameters to a DOS or BIOS service) you specify Regs.AH.

A drawing of the internals of the Turbo Pascal Registers type is shown in Figure 1, reprinted from my book, Complete Turbo Pascal, Third Edition. Each variant maps a different structure on the same 20 bytes of memory. The 0 variant partitions those 20 bytes into ten 2-byte words. The 1 variant treats the first eight bytes as single-byte quantities --and ignores the remaining 12 bytes.

The Modula-2 version provides the additional service of subdividing the Flags register into the 16 separate bits of a Modula-2 BITSET type, allowing individual flags to be tested by the very intuitive set operators.

In summary, what both Registerstypes provide are two different interpretations of the same region of memory, safely and with zero overhead in processor cycles. Like Pizza Terra and Pete Citera, it all depends on how you look at (or listen to) things.

Boxes and Bars

Now, as the retired Tibetan nomad might have said after a big dinner, 'Nuff yak. Code is It, as John Sculley didn't say --keep your soda pop salesmen straight --but I will. And here's some more.

In last month's column I presented the core of a virtual screens module in Turbo Pascal. The idea, to recap briefly, is to create a virtual screen on the heap, the size of an 8 1/2-inch by 11-inch piece of paper, and use the full display screen as a window onto that virtual screen.

The core unit, SCREENS.PAS, presented primitives for initializing and disposing of virtual screens, clearing them and writing to them, and panning the visible display up and down a virtual screen. In this issue I'm providing the beginning of a second-level unit, VTOOLS.PAS, (Listing Two) to contain higher-level display routines like forms and menus. In the future, when I say, "Add this procedure to the VTOOLS unit," you'll know what I mean.

Building things on the screen demands easy access to the PC's line-drawing characters, and this is what VTOOLS provides in this first iteration. The characters come in two forms: single-line and double-line. I've defined each character as a two-element array, indexed by the Boolean constants Single-Line and DoubleLine, with the sense that DoubleLine (True) selects the double-line characters. The character definitions are stored in a record constant whose fields are of type LineChars. Picking a specific character from the record is done this way:

  BoxChars.LLCorner[SingleLine]

This expression "cooks down" to character 192 --and makes a lot more sense when you read the code. It's often useful to have vertical and horizontal bars within easy reach for drawing forms and boxes. I've provided an array type called BarStrings containing two 255-character long STRING types, again indexed by those same two Boolean constants SingleLine and DoubleLine. For example, when you need a long string of single-line vertical bars, you would use this expression:

  VBars[SingleLine]

To generate a bar shorter than 255 characters, you would use the Copy function:

Copy(HBars[DoubleLine], 1, BarLength)

Having a string full of vertical bar characters is pointless if there's no way to write that string to the screen ... vertically. So, Listing Three, (WRTDOWN. SRC) provides vertical string display to virtual screens. Add WriteDownTo to the SCREENS.PAS source code file presented last issue! If you don't, this month's code will not compile.

Prime user of WriteDownTo is MakeBox, which is quite straightforward and fast. MakeBox draws boxes on a virtual screen, in either single-line or double-line form as selected. To demonstrate MakeBox, try BOXTEST.PAS (Listing Four), which I adapted from Complete Turbo Pascal to operate with virtual screens. BoxTest just throws random-sized boxes all over the place, and allows you to pan through the chaos without interrupting it --demonstrating that virtual screens can be easily examined in media res without disrupting ongoing work. Run BoxTest and pan with the up/down arrow keys --you'll be seeing boxes before your eyes for some time to come.

A Never Ending Life Saver

There's a funny fantasy story by Bernard Wolfe called "The Never Ending Penny" in which a man wishes that there would always be a penny in his pocket. He gets his wish, and no matter how many times he pulls the penny from his pocket, there is always another there. Unfortunately, it takes a lot of pennies to buy anything useful, and when he complains that his arm is fit to fall off from pulling pennies out of his pocket, his fairy godmother chides him to be glad he didn't wish for a never ending Wintergreen Lifesaver -- the chap who did soon weighed 300 pounds.

In reading through the documentation for Turbo Power Software's new Turbo Professional 5.0, I felt that no matter how many routines I pulled out, there would always be another one there. The range of this toolkit package is simply astonishing. Just a few of its video-related abilities include mouse support, a popup help system, pick lists and menus, and (gasp) a virtual screen system. (I guess --fortunately for this column --Kim & Company don't share my obsession with 66-line screens.) Also in the package are data entry screen support, extended and expanded memory support, long ASCIIZ strings, critical error handlers, in-memory sorts, and virtual data arrays up to 32 Mbytes in size. The full $125 price of the package, however, may well be worth a single section of the product devoted to interrupt service routines and TSRs.

Interrupts and TSRs are the LaBrea tarpits of our industry; no matter how many times newcomer programmers are reminded how dangerous such criters are, they march into the tar up to the neck and then squeak when they get stuck. In Turbo Professional's scheme you write your program as a single, large procedure and then drop the procedure into a very tidy TSR program shell. The whole thing, when run, loads your procedure as a TSR and handles all the hairy stuff like assigning hot-keys and keeping DOS from meeting itself in a dark alley with bloody results.

I don't have time to say more, but I will say that if you intend to write TSRs you must get this package. All the source is present, so if you choose to, (and you should, for your own protection) you can read the source and understand all the considerable magic going on beneath the surface.

To say "highly recommended" abuses the word "highly."

Cheaper Chips

While curled up with the Sunday paper this morning, I saw that Fry's is selling 1MB 100ns DRAMs for $24.95, making a bank of nine cost only $225 --down by a third from when I last looked. Maybe it's time to see for myself if OS/2 really is half an operating system, or truly the next generation. Certainly it's time for a trip to Fry's, which (for those of you who haven't heard) was a drugstore with a computer corner that eventually became a computer store with a toothpaste-and-Kleenex aisle.

Besides, I think I'm out of Ultra Brite.

Availability

All source code for articles in this issue is available on a single disk. To order, send $14.95 (Calif. residents add sales tax) to Dr. Dobb's Journal, 501 Galveston Dr., Redwood City, CA 94063, or call 800-356-2002 (inside Calif.) or 800-533-4372 (outside Calif.). Please specify the issue number and format (MS-DOS, Macintosh, Kaypro).

Products Mentioned

Turbo Professional 5.0 Turbo Power Software P.O. Box 66747 Scotts Valley, CA 95066-0747 408-438-8608

_Structured Programming Column_ by Jeff Duntemann

[LISTING ONE]

<a name="00fe_000f">

{ Turbo Pascal 4.0/5.0 Registers type, from the DOS unit: }

Registers = RECORD
              CASE Integer OF
                0 : (AX,BX,CX,DX,BP,SI,DI,DS,ES,Flags : Word;
                1 : (AL,AH,BL,BH,CL,CH,DL,DH          : Byte;
            END;

(* TopSpeed Modula 2's Registers type, from the SYSTEM module: *)

Registers = RECORD
              CASE : BOOLEAN OF
              | TRUE  : AX,BX,CX,DX,BP,SI,DI,DS,ES : CARDINAL;
                        Flags                      : BITSET;
              | FALSE : AL,AH,BL,BH,CL,CH,DL,DH    : SHORTCARD;
              END;
            END;





<a name="00fe_0010"><a name="00fe_0010">
<a name="00fe_0011">
[LISTING TWO]
<a name="00fe_0011">

{--------------------------------------------------------------}
{                          VTOOLS                              }
{                                                              }
{               Virtual screen I/O tools unit                  }
{                                                              }
{                                    by Jeff Duntemann KI6RA   }
{                                    Turbo Pascal 5.0          }
{                                    Last modified 1/17/89     }
{--------------------------------------------------------------}

UNIT VTools;

INTERFACE

USES DOS,        { Standard Borland unit }
     TextInfo,   { Given in DDJ 3/89     }
     Screens;    { Given in DDJ 4/89     }

CONST
  SingleLine  = False;   { To specify single vs. double line }
  DoubleLine  = True;    { bars and boxes }

TYPE
  LineChars   = ARRAY[SingleLine..DoubleLine] OF Char;
  BarStrings  = ARRAY[SingleLine..DoubleLine] OF String;
  BoxRec      = RECORD
                  ULCorner,   { Each field in this record  }
                  URCorner,   {  contains both the single  }
                  LLCorner,   {  line and double line      }
                  LRCorner,   {  form of the named line    }
                  HBar,       {  character, indexed by     }
                  VBar,       {  the Boolean constants     }
                  LineCross,  {  SingleLine and DoubleLine }
                  TDown,      {  defined above.            }
                  TUp,
                  TRight,
                  TLeft : LineChars
                END;

CONST
  BoxChars    : BoxRec =
                (ULCorner  : (#218,#201);   { Z  I  }
                 URCorner  : (#191,#187);   { ?  ;  }
                 LLCorner  : (#192,#200);   { @  H  }
                 LRcorner  : (#217,#188);   { Y  <  }
                 HBar      : (#196,#205);   { D  M  }
                 VBar      : (#179,#186);   { D  :  }
                 LineCross : (#197,#206);   { E  N  }
                 TDown     : (#194,#203);   { B  K  }
                 TUp       : (#193,#202);   { A  J  }
                 TRight    : (#195,#185);   { C  9  }
                 TLeft     : (#180,#204));  { 4  L  }


VAR
  HBars       : BarStrings;   { Horizontally oriented bars }
  VBars       : BarStrings;   { Vertically oriented bars   }


PROCEDURE MakeBox(Target           : ScreenPtr;
                  X,Y,Width,Height : Integer;
                  IsSingleLine     : Boolean);


IMPLEMENTATION


PROCEDURE MakeBox(Target           : ScreenPtr;
                  X,Y,Width,Height : Integer;
                  IsSingleLine     : Boolean);

BEGIN
  GotoXY(Target,X,Y);
  WITH BoxChars DO
    BEGIN
      { Display the top line: }
      WriteTo(Target,ULCorner[IsSingleLine]+
                     Copy(HBars[IsSingleLine],1,Width-2)+
                     URCorner[IsSingleLine]);
      { Display the left side: }
      GotoXY(Target,X,Y+1);
      WriteDownTo(Target,Copy(VBars[IsSingleLine],1,Height-2));
      { Display the right side: }
      GotoXY(Target,X+Width-1,Y+1);
      WriteDownTo(Target,Copy(VBars[IsSingleLine],1,Height-2));
      { Display the bottom line: }
      GotoXY(Target,X,Y+Height-1);
      WriteTo(Target,LLCorner[IsSingleLine]+
                     Copy(HBars[IsSingleLine],1,Width-2)+
                     LRCorner[IsSingleLine]);
    END;
END;


{ VTOOLS Initialization Section }

BEGIN
{ This fills the predefined HBars/VBars variables with line characters: }
  FillChar(HBars[SingleLine],
           SizeOf(HBars[SingleLine]),
           BoxChars.HBar[SingleLine]);
  FillChar(HBars[DoubleLine],
           SizeOf(HBars[DoubleLine]),
           BoxChars.HBar[DoubleLine]);
  HBars[SingleLine,0] := Chr(255);
  HBars[DoubleLine,0] := Chr(255);

  FillChar(VBars[SingleLine],
           SizeOf(VBars[SingleLine]),
           BoxChars.VBar[SingleLine]);
  FillChar(VBars[DoubleLine],
           SizeOf(VBars[DoubleLine]),
           BoxChars.VBar[DoubleLine]);
  VBars[SingleLine,0] := Chr(255);
  VBars[DoubleLine,0] := Chr(255);
END.





<a name="00fe_0012"><a name="00fe_0012">
<a name="00fe_0013">
[LISTING THREE]
<a name="00fe_0013">

{ V-Screen procedure that writes from X,Y downward: }

PROCEDURE WriteDownTo(Target : ScreenPtr; S : String);

VAR
  I,K         : Integer;
  TY          : Byte;
  ShiftedAttr : Word;

BEGIN
  { Put attribute in the high byte of a word: }
  ShiftedAttr := CurrentAttr SHL 8;
  WITH Target^ DO
    BEGIN
      TY := Y;
      K := 0;
      FOR I := 0 TO Length(S)-1 DO
        BEGIN
          IF Y+I > VHEIGHT THEN  { If string goes past bottom of screen, }
            BEGIN                { we wrap: }
              Inc(X);            { Increment X value  }
              Y := 1; TY := 1;   { Reset Y and temp Y value to 1 }
              K := 0;            { K is the line-offset counter  }
            END;
          { Here we combine the character from the string and the   }
          { current attribute via OR, and assign it to its location }
          { on the screen: }
          Word(ShowPtrs[Y+K]^[X]) := Word(S[I+1]) OR ShiftedAttr;
          Inc(TY); Inc(K);
        END;
      Y := TY;   { Update Y value in descriptor record }
    END
END;





<a name="00fe_0014"><a name="00fe_0014">
<a name="00fe_0015">
[LISTING FOUR]
<a name="00fe_0015">

{--------------------------------------------------------------}
{                          BoxTest                             }
{                                                              }
{              Character box draw demo program                 }
{                                                              }
{                                    by Jeff Duntemann KI6RA   }
{                                    Turbo Pascal V5.0         }
{                                    Last update 1/21/89       }
{--------------------------------------------------------------}

PROGRAM BoxTest;

USES Crt,           { Standard Borland unit }
     Screens,       { Given in DDJ; 4/89    }
     VTools;        { Given in DDJ; 5/89    }


VAR
  WorkScreen   : Screen;
  MyScreen     : ScreenPtr;
  X,Y          : Integer;
  Width,Height : Integer;
  Count        : Integer;
  Ch           : Char;
  Quit         : Boolean;

BEGIN
  Randomize;   { Seed the pseudorandom number generator }
  MyScreen := @WorkScreen;    { Create a pointer to WorkScreen }
  InitScreen(MyScreen,True);
  ClrScreen(MyScreen,ClearAtom);     { Clear the entire screen }
  Quit := False;

  REPEAT                    { Draw boxes until "Q" is pressed: }
    IF Keypressed THEN      { If a keystroke is detected }
      BEGIN
        Ch := ReadKey;      { Pick up the keystroke }
        IF Ord(Ch) = 0 THEN { See if it's an extended keystroke }
          BEGIN
            Ch := ReadKey;  { If so, pick up scan code }
            CASE Ord(Ch) OF { and parse it }
              72 : Pan(MyScreen,Up,1);   { Up arrow }
              80 : Pan(MyScreen,Down,1); { Down arrow }
            END { CASE }
          END
        ELSE     { If it's an ordinary keystroke, test for quit: }
          IF Ch IN ['Q','q'] THEN Quit := True
      END;
    { Now we draw a random box. }
    { First get random X/Y position on the virtual screen: }
    REPEAT X := Random(VWIDTH-5) UNTIL X > 1;
    REPEAT Y := Random(VHEIGHT-5) UNTIL Y > 1;
    { Next get a random width and height to avoid wrapping: }
    REPEAT

      Width  := Random(VWIDTH)
    UNTIL (Width > 1) AND ((X + Width) < VWIDTH);;
    REPEAT
      Height := Random(VHEIGHT)
    UNTIL (Height > 1) AND ((Y + Height) < VHEIGHT);;
    { Draw the box: }
    MakeBox(MyScreen,X,Y,Width,Height,DoubleLine);  { and draw it! }
  UNTIL Quit
END.












Copyright © 1989, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.