Spider | Previous | Next | Full Table of Contents | Index | Program List | Copyright

7.7 Continuing Saga: Inside the Spider Package

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
Body of the Spider Package

WITH Ada.Text_IO;
WITH Screen; 
PACKAGE BODY Spider IS
------------------------------------------------------------------------
--| This package provides procedures to emulate "Spider" 
--| commands. The spider 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;
 

State Variables and Coordinate Transformations

The first few lines of the package body define a type 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.

The Spider's Actual Commands

Having examined all the service subprograms, we proceed to the actual spider commands, that is, the ones given in the package specification. 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.


Spider | Previous | Next | Full Table of Contents | Index | Program List | Copyright

Copyright ©1996 by Addison-Wesley Publishing Company, Inc.