In Section 10.6 we developed the package
specification Spiders
for
multiple spiders (
Program
10.19); we left the body as an exercise. However, we could not, at that
time, consider how to program the spiders so that they acted independently of
each other, crawling at will around the room. In this section, we show how to
develop multiple concurrent spiders.
Me
. The main loop of that spider's life is given by:
LOOP FOR Count IN 1..Random_20.Random (Gen => G) LOOP BEGIN -- to handle exception Spiders.Step(Me); EXCEPTION WHEN Spiders.Hit_the_Wall => -- turn around Spiders.Right (Me); Spiders.Right (Me); WHEN Spiders.Hit_a_Spider => -- turn right Spiders.Right (Me); END; END LOOP; Spiders.Right (Me); END LOOP;
In
an endless loop, the spider first selects a random number of steps in the range
1..20, then tries to step forward that number of times. If it hits the wall
(Spiders.Hit_the_Wall
is raised), it turns around and keeps
stepping in the opposite direction; if it bumps into another spider
(Spiders.Hit_a_Spider
is raised), it turns right and continues its
walk.
Program
16.7 shows a full program in which this loop is incorporated in a task type
Drunken_Spider_Task
, with a discriminant MyColor
and
a "start button" entry called Hatch
. The discriminant has a
default value Spiders.Black
; this means that if a spider object
declaration fails to provide a value for the discriminant, the default value
will be taken.
Program 16.7
WITH Spiders; WITH Ada.Text_IO; WITH Ada.Numerics.Discrete_Random; PROCEDURE Drunken_Spiders IS ------------------------------------------------------------------------ --| Multiple drunken spiders try to tour their room. --| The spiders are represented as task objects. --| Author: Michael B. Feldman, The George Washington University --| Last Modified: December 1995 ------------------------------------------------------------------------ SUBTYPE RandomSteps IS Positive RANGE 1..20; PACKAGE Random_20 IS NEW Ada.Numerics.Discrete_Random (Result_Subtype => RandomSteps); G: Random_20.Generator; PACKAGE RandomHeading IS NEW Ada.Numerics.Discrete_Random (Result_Subtype => Spiders.Directions); D: RandomHeading.Generator; -- Now a spider is a task object, as defined by this type. -- Note: default color is black. TASK TYPE Drunken_Spider_Task (MyColor: Spiders.ScreenColors := Spiders.Black) IS -- one "start button" entry to bring spider to life ENTRY Hatch; END Drunken_Spider_Task; TASK BODY Drunken_Spider_Task IS Me: Spiders.Spider; BEGIN -- Drunken_Spider_Task ACCEPT Hatch; -- come to life here -- Randomize all starting parameters Spiders.Start (Which => Me, Row => Random_20.Random(Gen => G), Col => Random_20.Random(Gen => G), WhichColor => MyColor, WhichWay => RandomHeading.Random(Gen => D)); LOOP -- Spider will count steps correctly but might change direction FOR Count IN 1..Random_20.Random (Gen => G) LOOP BEGIN -- to handle exception Spiders.Step(Me); EXCEPTION WHEN Spiders.Hit_the_Wall => -- turn around Spiders.Right (Me); Spiders.Right (Me); WHEN Spiders.Hit_a_Spider => -- turn right Spiders.Right (Me); END; END LOOP; Spiders.Right (Me); END LOOP; EXCEPTION WHEN OTHERS => Ada.Text_IO.Put(Item => "This spider is dying."); Ada.Text_IO.New_Line; END Drunken_Spider_Task; -- Now declare some spider objects Charlotte : Drunken_Spider_Task(MyColor => Spiders.Green); Murgatroyd: Drunken_Spider_Task(MyColor => Spiders.Red); Arachne : Drunken_Spider_Task(MyColor => Spiders.Blue); BEGIN -- Drunken_Spiders Spiders.DrawRoom; -- Bring the spiders to life, then stand back and watch! Charlotte.Hatch; Murgatroyd.Hatch; Arachne.Hatch; END Drunken_Spiders;
Within the task body is a local variable
Me: Spiders.Spider;which is referred to further in the task body. If we declare multiple objects of type
Drunken_Spider_Task
, each one will have its own Me
variable.
The rest is straightforward: Each spider waits at its ACCEPT
to be
hatched, then calls Spider.Start
with random values for the
starting direction and position. At this point, the spider starts executing its
main loop.
In Drunken_Spiders
, three Drunken_Spider_Task
objects are declared with their respective color discriminants. After the main
BEGIN
, each spider is brought to life, and nothing is left for the
main program to do but watch the action.
Algorithm for Spider Move
1. Compute location into which spider is trying to move
2. IF
the spider is trying to move to an occupied square
THEN
3. RAISE Hit_a_Spider
ELSE
4. Step out of the current space into the unoccupied square
END IF;
Step 4 can be refined into
Step 4 Refinement
4.1 Draw a colored mark in the current square
4.2 Mark the current space as unoccupied
4.3 Mark the new space as occupied
4.4 Draw a spider symbol in the new square
There are therefore several operations to be done to record the spider's move,
involving both the screen and the room board in which we keep track of
occupancy. Because several spiders are crawling around concurrently, we must be
sure that Steps 4.1 through 4.4 are done as a single operation. Consider a
situation in which Murgatroyd
executes its Step 4.1 and 4.2,
vacating its square. but then--before Murgatroyd
actually moves to
its new space in Steps 4.3 and 4.4--Charlotte
is able to move into
that same square because it is not yet marked as occupied. This is not a very
good situation--it leaves Murgatroyd
without a square.
This is another example of a situation in which mutual exclusion is
necessary. We can handle this by analogy with the screen protector from
Program
16.6. We will define a protected type which "owns" the room array, with a
procedure Move
to encapsulate Steps 4.1 through 4.4 in one
protected operation.
Let us add to the body of Spiders
the following declarations:
TYPE Status IS (Unoccupied, Occupied); TYPE BoardType IS ARRAY (RoomHeight, RoomWidth) OF Status; PROTECTED TYPE Room_Type IS PROCEDURE Move (Which: IN OUT Spider; HowMany: IN Natural); PRIVATE RoomBoard: BoardType := (OTHERS => (OTHERS => Unoccupied)); END Room_Type; PROTECTED BODY Room_Type IS SEPARATE; Room: Room_Type;Our protected type
Room_Type
has some memory, RoomBoard
,
that belongs to it exclusively, as indicated in the PRIVATE
section. Move
is a protected operation; a given call of
Move
will be executed in its entirety before another
Move
can be started. The declarations above show the protected
body as a separate subunit; the subunit is given in full as
Program
16.8.
Program 16.8
SEPARATE (Spiders) PROTECTED BODY Room_Type IS ------------------------------------------------------------------ --| Body of protected type for the spider's room. --| The room array is protected from concurrent access by --| requiring all access to be via the protected procedure Move. --| Author: Michael B. Feldman, The George Washington University --| Last Modified: December 1995 ------------------------------------------------------------------ PROCEDURE Move (Which: IN OUT Spider; HowMany: IN Natural) IS Row: RoomHeight; Column: RoomWidth; BEGIN -- Move -- If out of bounds raise exception. IF NearWall (Which, HowMany) THEN RAISE Hit_the_Wall; END IF; Row := Which.CurrentRow; Column := Which.CurrentColumn; -- Compute new proposed location CASE Which.Heading IS WHEN North => Row := Which.CurrentRow - HowMany; WHEN East => Column := Which.CurrentColumn + HowMany; WHEN South => Row := Which.CurrentRow + HowMany; WHEN West => Column := Which.CurrentColumn - HowMany; END CASE; -- Is this space occupied? IF RoomBoard(Row, Column) = Occupied THEN RAISE Hit_a_Spider; ELSE -- put a block down where spider is standing; vacate space DrawSymbol(Which => Which, WhichChar => ColorSymbols(Which.Ink)); RoomBoard(Which.CurrentRow, Which.CurrentColumn) := Unoccupied; -- occupy new space RoomBoard(Row, Column) := Occupied; Which.CurrentRow := Row; Which.CurrentColumn := Column; ShowSpider (Which); END IF; END Move; END Room_Type;
All
that remains is to modify the bodies of those operations in the
Spiders
package body which involve a move. Here is
Jump
, for example:
PROCEDURE Jump (Which: IN OUT Spider; HowMany: IN Positive) IS BEGIN -- Concurrent spiders now, so move must be protected. Room.Move(Which, HowMany); IF Debugging = On THEN -- if debug mode, wait for user to press RETURN Ada.Text_IO.Skip_Line; ELSE DELAY 0.1; END IF; END Jump;We leave it to you as an exercise to complete the package body.
SYNTAX DISPLAY
Protected Type Specification
PROTECTED TYPE pname (optional list of discrimnents ) IS specifications of functions, procedures, and entries . . . PRIVATE declarations of encapsulated data structures END pname;
Room_Type
and
ScreenManagerType
specifications in this chapter.
PRIVATE
part is optional.PROTECTED BODY pname IS BEGIN entry, procedure, and function bodies END pname;
Room_Type
and ScreenManagerType
bodies in
this chapter.
Finally, we show an example of how tasks really do have aspects of data objects. Program 16.9 shows how we could create an array of spider objects.
Program 16.9
WITH Spiders; WITH Ada.Text_IO; WITH Ada.Numerics.Discrete_Random; PROCEDURE Drunken_Spiders_Family IS ------------------------------------------------------------------ --| Multiple drunken spiders try to tour their room. --| The spiders are represented as task objects; --| a spider family is represented by an array of spiders. --| Author: Michael B. Feldman, The George Washington University --| Last Modified: December 1995 ------------------------------------------------------------------ SUBTYPE RandomSteps IS Positive RANGE 1..20; PACKAGE Random_20 IS NEW Ada.Numerics.Discrete_Random (Result_Subtype => RandomSteps); G: Random_20.Generator; PACKAGE RandomHeading IS NEW Ada.Numerics.Discrete_Random (Result_Subtype => Spiders.Directions); D: RandomHeading.Generator; -- Now a spider is a task object, as defined by this type. TASK TYPE Drunken_Spider_Task (MyColor: Spiders.ScreenColors := Spiders.Black) IS -- one "start button" entry to bring spider to life ENTRY Hatch; END Drunken_Spider_Task; TASK BODY Drunken_Spider_Task IS Me: Spiders.Spider; BEGIN -- Drunken_Spider_Task ACCEPT Hatch; -- come to life here -- Randomize all starting parameters Spiders.Start (Which => Me, Row => Random_20.Random(Gen => G), Col => Random_20.Random(Gen => G), WhichColor => MyColor, WhichWay => RandomHeading.Random(Gen => D)); LOOP -- Spider will count steps correctly but might change direction FOR Count IN 1..Random_20.Random (Gen => G) LOOP BEGIN -- to handle exception Spiders.Step(Me); EXCEPTION WHEN Spiders.Hit_the_Wall => -- turn around Spiders.Right (Me); Spiders.Right (Me); WHEN Spiders.Hit_a_Spider => -- turn right Spiders.Right (Me); END; END LOOP; Spiders.Right (Me); END LOOP; EXCEPTION WHEN OTHERS => Ada.Text_IO.Put(Item => "This spider is dying."); Ada.Text_IO.New_Line; END Drunken_Spider_Task; SUBTYPE FamilyRange IS Positive RANGE 1..10; TYPE FamilyType IS ARRAY (FamilyRange) OF Drunken_Spider_Task; Family: FamilyType; -- now we have an entire array of spiders BEGIN -- Drunken_Spiders_Family Spiders.DrawRoom; -- Bring the spiders to life, then stand back and watch! FOR Which IN FamilyRange LOOP Family(Which).Hatch; END LOOP; END Drunken_Spiders_Family;
Instead of declaring named spider variables as we did in Program 16.7, we give a few new declarations:
SUBTYPE FamilyRange IS Positive RANGE 1..10; TYPE FamilyType IS ARRAY (FamilyRange) OF Drunken_Spider_Task; Family: FamilyType; -- now we have an entire array of spiders
Here we declare Family
as a ten-element array of
spider task objects. As in the case of ordinary task variables, the entire
array of spiders is activated just after the main BEGIN
. We then
cause all the spiders to hatch by using a simple FOR
loop:
FOR Which IN FamilyRange LOOP Family(Which).Hatch; END LOOP;
Varying the bounds of FamilyRange
is sufficient to
change the size of the spider family.
Copyright © 1996 by Addison-Wesley Publishing Company, Inc.