You have seen a number of examples of how to use the spider package, whose
specification was given as
Program
3.11. You have not yet looked inside the body of the package; you now have
enough background to understand the body. This package uses much of the
material discussed in this chapter, including numeric subtypes,
Boolean
expressions, CASE
statements, and the screen
package.
Program
7.11 shows the body of this package. It is quite long, but we can examine
it in sections.
Program 7.11
WITH Ada.Text_IO; WITH Screen; PACKAGE BODY Spider IS ------------------------------------------------------------------------ --| This package provides procedures to emulate "Spider" --| commands. The spider is can move around --| the screen drawing simple patterns. --| Author: John Dalbey, Cal Poly San Luis Obispo, 1992 --| Adapted by: Michael B. Feldman, The George Washington University --| Last Modified: July 1995 ------------------------------------------------------------------------ TYPE ScreenColors IS (Red, Blue, Green, Black); -- available colors -- Spider's View of her Room - rows and cols both numbered 1..20 SUBTYPE Rows IS Positive RANGE 1..20; SUBTYPE Cols IS Positive RANGE 1..20; RowsInRoom : CONSTANT Positive := Rows'Last; ColsInRoom : CONSTANT Positive := Cols'Last; -- Spider State Spidersym : CONSTANT character := '*'; -- asterisk CurrentColumn: Cols; -- spider's position CurrentRow : Rows; -- in the room. Heading : Directions; -- spider's direction Ink : ScreenColors; -- spider's color -- Screen Description Constants: for 24 x 80 screen, -- 1 spider row = 1 screen row, 1 spider col = 2 screen cols RowLow : CONSTANT Screen.Depth := 2; -- room row bounds RowHigh : CONSTANT Screen.Depth := RowLow + Rows'Last; ColLow : CONSTANT Screen.Width := 21; -- lower column bound ColHigh : CONSTANT Screen.Width := ColLow + 2*Cols'Last; DebugFlag : Boolean := False; -- Is single stepping on? -- internal procedures and functions, not in specification -- and therefore not available to client program PROCEDURE DrawSymbol (Which: Character) IS -- Pre: Which is defined -- Post: Which appears in its proper position on the screen BEGIN Screen.MoveCursor (Row => (RowLow - 1) + CurrentRow, Column => (ColLow - 2) + (2 * CurrentColumn)); Ada.Text_IO.Put (Item => Which); Ada.Text_IO.New_Line; END DrawSymbol; FUNCTION ColorSymbols (Color: ScreenColors) RETURN Character IS -- Pre: Color is defined -- Post: Returns the drawing character corresponding to Color BEGIN CASE Color IS WHEN Red => RETURN '+'; WHEN Blue => RETURN 'X'; WHEN Green => RETURN 'O'; WHEN Black => RETURN '.'; END CASE; END ColorSymbols; FUNCTION Compass (Direction: Directions) RETURN Character IS -- Pre: Direction is defined -- Post: Returns drawing character corresponding to Direction BEGIN CASE Direction IS WHEN North => RETURN '^'; WHEN East => RETURN '>'; WHEN South => RETURN 'v'; WHEN West => RETURN '<'; END CASE; END Compass; PROCEDURE DrawStatus IS -- Pre: None -- Post: Status Box appears on the screen BEGIN Screen.MoveCursor (Row => 2, Column => 1); Ada.Text_IO.Put (" --- "); Screen.MoveCursor (Row => 3, Column => 1); Ada.Text_IO.Put ("| |"); Screen.MoveCursor (Row => 4, Column => 1); Ada.Text_IO.Put ("| |"); Screen.MoveCursor (Row => 5, Column => 1); Ada.Text_IO.Put (" --- "); END DrawStatus; PROCEDURE DrawRoom IS -- Pre: None -- Post: Room appears on the screen BEGIN Screen.ClearScreen; Screen.MoveCursor (Row => 1, Column => 1); -- Top Bar Ada.Text_IO.Put (" "); Ada.Text_IO.Put (" --------------------------------------- "); Ada.Text_IO.New_Line; FOR I in 1..20 LOOP Ada.Text_IO.Put (" "); Ada.Text_IO.Put ("|. . . . . . . . . . . . . . . . . . . .|"); Ada.Text_IO.New_Line; END LOOP; Ada.Text_IO.Put (" "); Ada.Text_IO.Put (" --------------------------------------- "); DrawStatus; END DrawRoom; PROCEDURE ChgColor (NewColor : ScreenColors) IS -- Pre: NewColor is defined -- Post: Ink is changed to NewColor and displayed in status box BEGIN Ink := NewColor; Screen.MoveCursor ( Row => 4, Column => 3); Ada.Text_IO.Put (ColorSymbols(Ink)); END ChgColor; PROCEDURE ShowDirection IS -- Pre: None -- Post: Heading is displayed in the status box BEGIN Screen.MoveCursor(Row => 3,Column => 3); Ada.Text_IO.Put (Compass(Heading)); END ShowDirection; PROCEDURE ShowSpider IS -- Pre: None -- Post: The spider symbol appears in its current position BEGIN DrawSymbol (SpiderSym); END ShowSpider; -- These procedures are in the package specification -- and implement the "official" spider commands PROCEDURE Start IS BEGIN DrawRoom; CurrentColumn := 10; -- these are in the spider's view CurrentRow := 11; Heading := North; Green; ShowSpider; ShowDirection; END Start; PROCEDURE Blue IS BEGIN ChgColor (blue); END Blue; PROCEDURE Green IS BEGIN ChgColor (green); END Green; PROCEDURE Red IS BEGIN ChgColor (red); END Red; PROCEDURE Black IS BEGIN ChgColor (black); END Black; PROCEDURE Right IS BEGIN IF Heading = Directions'Last THEN Heading := Directions'First; ELSE Heading := Directions'Succ (Heading); END IF; ShowDirection; END Right; PROCEDURE Face (WhichWay: IN Directions) IS BEGIN Heading := WhichWay; ShowDirection; END Face; FUNCTION IsFacing RETURN Directions IS BEGIN RETURN Heading; END IsFacing; FUNCTION AtWall RETURN Boolean IS BEGIN -- Check for out of bounds (in the spider's view) CASE Heading IS WHEN North => RETURN CurrentRow <= Rows'First; WHEN East => RETURN CurrentColumn >= Cols'Last; WHEN South => RETURN CurrentRow >= Rows'Last; WHEN West => RETURN CurrentColumn <= Cols'First; END CASE; END AtWall; PROCEDURE Step IS BEGIN -- leave a track where spider is standing DrawSymbol (ColorSymbols (Ink) ); -- If out of bounds raise exception. IF AtWall THEN Screen.Beep; Ada.Text_IO.New_Line; RAISE Hit_the_Wall; END IF; -- change the spider's location CASE Heading IS WHEN North => CurrentRow := CurrentRow - 1; WHEN East => CurrentColumn := CurrentColumn + 1; WHEN South => CurrentRow := CurrentRow + 1; WHEN West => CurrentColumn := CurrentColumn - 1; END CASE; -- draw the spider in her new location ShowSpider; -- if debug mode, wait for user to press RETURN IF Debugging = On THEN Ada.Text_IO.Skip_Line; ELSE DELAY 0.5; Ada.Text_IO.New_Line; END IF; END Step; PROCEDURE Quit IS -- Quit command. BEGIN Screen.MoveCursor(Row => 23,Column => 1); END Quit; PROCEDURE Debug (Setting: Switch) is -- Toggle debugging mode BEGIN IF Setting = ON THEN DebugFlag := true; Screen.MoveCursor (Row => 10,Column => 1); Ada.Text_IO.Put ("-- DEBUG ON -- "); Ada.Text_IO.New_Line; Ada.Text_IO.Put (" Press Enter"); ELSE DebugFlag := false; Screen.MoveCursor (Row => 10,Column => 1); Ada.Text_IO.Put (" "); Ada.Text_IO.New_Line; Ada.Text_IO.Put (" "); END IF; END Debug; FUNCTION Debugging RETURN Switch IS BEGIN IF DebugFlag THEN RETURN On; ELSE RETURN Off; END IF; END Debugging; END Spider;
ScreenColors
as an enumeration type, then describe the spider's view of its environment. The
spider's room has 20 rows (RowsInRoom
) and 20 columns
(ColsInRoom
), defined in terms of the positive subtypes
Rows
and Cols
. Next we have the spider's own symbol,
an asterisk ('*'
), and four variables which describe the current
location, direction, and color of the spider. These variables together comprise
the spider's state, that is, all its characteristics that can change
during the life of the program. The variables are therefore called state
variables.
The next four lines of the package describe the location and size of the actual room picture on the screen. The upper left corner of the room is at row = 2, column = 21. This corresponds to the spider's row = 1, column = 1. The spider's row = 20, column = 20, corresponds to the screen coordinates row = 22 (2 + 20), column 61 (21 + 2*20).
Why are we multiplying the columns by 2? On the terminal screen, columns are narrower than rows, so to make the room look square, we use alternating screen columns.
We have two sets of coordinates, the spider's coordinates (a row/column pair as viewed by the spider) and the room's physical coordinates on the screen (a different row/column pair as seen on the screen). As we will see below, several of the package procedures have the responsibility to convert between the coordinate systems. This is a simple example of coordinate transformation, a concept often used in computer graphics and other engineering applications.
Now let's move on to the first groups of subprograms. These are included in
the package only to provide services to other subprograms and are not seen by a
program that uses the package. The first procedure is DrawSymbol
,
which draws a character in the spot on the screen that represents the spider's
current location. Note how the parameters to Screen.MoveCursor
are
computed. For example, the spider's row 1 is the screen's row 2; the spider's
column 10 is the screen's column 39 (19 + 2*10). The call to
Ada.Text_IO.New_Line
is present because in many operating systems
output is buffered, that is, nothing is actually displayed on the screen
until a line is complete. Including the New_Line
call therefore
indicates a complete line to the operating system, and the character is
displayed immediately.
You have noticed that the spider's color is displayed as a specific
character, and its direction by an "arrow" pointing in the correct direction.
The next two functions, ColorSymbols
and Compass
,
take care of the necessary transformations, each function using a
CASE
statement to determine the appropriate character.
The next two procedures, DrawStatus
and DrawRoom,
display the status box and the picture of the room, respectively, using
Screen.MoveCursor
, as we have seen before. Note that
DrawRoom
calls DrawStatus. ChgColor
changes the
spider's ink color and displays the new symbol in the status box;
ShowDirection
displays the appropriate direction indicator in that
box. Finally, ShowSpider
displays the spider's asterisk in its
current location, calling DrawSymbol
to do the coordinate
transformation.
Start
sets everything up, draws the room, and places the spider in
the center of the room (according to the spider's view). Next, the four color
commands all call ChgColor
to change the ink and display the new
character in the status box.
Right
changes the spider's heading, using the enumeration
attribute function that finds the successor of a value, then calls
ShowDirection
to display the arrow. Face
is similar,
and IsFacing
just returns the spider's current heading.
AtWall
uses a CASE
statement again to determine which
way the spider is facing, which is necessary in order to detect whether the
spider is about to hit the wall.
Step
is a bit longer than the other operations, first leaving a
track in the spider's position, then checking whether the spider has hit the
wall. If so, the terminal is made to beep (as with DrawSymbol
, the
New_Line
makes the beep occur immediately). The next statement,
RAISE Hit_the_Wall;is the first time we have seen
RAISE
used; in this case, it causes
Hit_the_Wall
to be raised immediately. Because there is no
exception handler in Step
, the procedure halts and returns to its
caller, where the exception is raised again. This is a good time to look back
at Drunken_Spider
(
Program
7.7) to review how the exception is handled there.
If the spider is not hitting the wall, it can take a step forward. This is done
by the CASE
statement in Step
, just changing the
current row or column according to the spider's heading. Finally, the spider
symbol is displayed in its new location. If the user's program has called
Spider.Debug
to set the debugging switch, the program now waits
for the user to press Enter
(using
Ada.Text_IO.Get_Line
, which we will examine in more detail in
Chapter 9); if debugging is not turned on, the
program just delays one-half
second and continues.
This has been a fairly long but interesting trip through the spider package; as you have seen, this packages pulls together many of the concepts introduced in this chapter and earlier ones.
Copyright © 1996 by Addison-Wesley Publishing Company, Inc.