-- FILE: life_game.adb -- AUTHOR: G. Levine -- DATE: December 1996 -- PURPOSE: the game of life implemented with tasks; -- specifically, each cell is implemented as a separate task with -- current cell condition (alive or dead) communicated only -- between cells. This implementation may allow extension to -- more complex cell states and distributed cells. -- There is an implementation limitation on the size of the -- grid, dependent upon the number of concurrent tasks that -- are supported. with TEXT_IO; package body LIFE_GAME is subtype ROW_RANGE is INTEGER range LOW1 .. HIGH1; subtype COLUMN_RANGE is INTEGER range LOW2 .. HIGH2; type COORDINATE is record Row : ROW_RANGE; Column : COLUMN_RANGE; end record; type NEIGHBOR_NUMBER is new INTEGER range 0 .. 8; -- for a two-dimensional implementation only type NEIGHBOR is array (NEIGHBOR_NUMBER) of COORDINATE; task type CELL is entry Initialization (Position : in COORDINATE; Status : in BOOLEAN); entry Update (in_Status : in BOOLEAN; out_Status: out BOOLEAN); entry Output (Done : in BOOLEAN; Status: out BOOLEAN); end CELL; type CELLS is array (ROW_RANGE, COLUMN_RANGE) of CELL; Life_Grid: CELLS; -- We implement LIFE as a two-dimensional array with conceptually -- unbounded range. Of course, implementation will limit the size -- rather drastically, depending upon the number of tasks that can be -- supported. type GRID is array (ROW_RANGE, COLUMN_RANGE) of BOOLEAN; procedure Start is package INT_IO is new TEXT_IO.INTEGER_IO (INTEGER); Display_Grid: GRID := (others => ( others => FALSE)); Row : INTEGER; Column : INTEGER; begin loop begin TEXT_IO.Put_Line("Enter your Coordinates." & " An out of range value terminates input"); INT_IO.Get (Row); INT_IO.Get (Column); exit when (Row < LOW1 or else Column < LOW2 or else Row > HIGH1 or else Column > HIGH2); Display_Grid (Row, Column) := TRUE; exception when TEXT_IO.DATA_ERROR => TEXT_IO.Put_Line ("Incorrect input. Try again"); TEXT_IO.Skip_Line; end; end loop; for Row in Life_Grid'Range(1) loop for Column in Life_Grid'Range(2) loop Life_Grid (Row, Column).Initialization ( Position => (Row, Column), Status => Display_Grid (Row, Column)); end loop; end loop; end Start; procedure Output_Grid (Life: GRID) is begin for Row in Life'Range (1) loop for Column in Life'Range (2) loop if Life (Row, Column) then TEXT_IO.Put (LIVE_MARK); else TEXT_IO.Put (DEAD_MARK); end if; end loop; TEXT_IO.New_Line; end loop; TEXT_IO.New_Line; end Output_Grid; task Synchronize; task body Synchronize is -- This task is used for synchronization so that some cells do not regenerate -- themselves quicker than others, as, for example, occurs in the human -- population. It is also used for synchronizing output and task termination. -- Generation synchronization could be removed and the output task can -- print the state of LIFE whenever it receives all of the values for -- the next generation, or, alternatively, at some recurring interval. Life : GRID; Answer : CHARACTER := 'y'; State : BOOLEAN; Done : BOOLEAN := FALSE; begin loop for Row in Life_Grid'Range(1) loop for Column in Life_Grid'Range(2) loop Life_Grid (Row, Column).Output (Done, State); Life (Row, Column) := State; end loop; end loop; exit when Done; Output_Grid (Life); TEXT_IO.Put ("Do you want to play again? "); TEXT_IO.Get (Answer); TEXT_IO.Skip_Line; -- Note that no other task does output, so that interruption will -- not affect results. Done := Answer = 'n' or else Answer = 'N'; end loop; end Synchronize; procedure Find_Neighbors (Pos : in COORDINATE; Call_Neighbors : out NEIGHBOR; Call_Count : out NEIGHBOR_NUMBER; Called_Count : out NEIGHBOR_NUMBER ) is begin if Pos.Row = LOW1 then -- top border if Pos.Row mod 2 = 0 then Call_Neighbors (1) := (Pos.Row + 1, Pos.Column); if Pos.Column = LOW2 then if Pos.Column mod 2 = 0 then Call_Neighbors(2) := (Pos.Row + 1, Pos.Column + 1); Call_Neighbors (3) := (Pos.Row , Pos.Column + 1); Call_Count := 3; Called_Count := 0; else -- odd column Call_Count := 1; Called_Count := 2; end if; elsif Pos.Column = HIGH2 then if Pos.Column mod 2 = 0 then Call_Neighbors(2) := (Pos.Row + 1, Pos.Column - 1); Call_Neighbors (3) := (Pos.Row, Pos.Column - 1); Call_Count := 3; Called_Count := 0; else Call_Count := 1; Called_Count := 2; end if; else -- interior column if Pos.Column mod 2 = 0 then Call_Neighbors (2) := (Pos.Row + 1, Pos.Column - 1); Call_Neighbors (3) := (Pos.Row + 1, Pos.Column + 1); Call_Neighbors (4) := (Pos.Row , Pos.Column + 1); Call_Neighbors (5) := (Pos.Row , Pos.Column - 1); Call_Count := 5; Called_Count := 0; else Call_Count := 1; Called_Count := 4; end if; end if; else -- odd row if Pos.Column = LOW2 then if Pos.Column mod 2 = 0 then Call_Neighbors (1) := (Pos.Row, Pos.Column + 1); Call_Neighbors (2) := (Pos.Row + 1, Pos.Column + 1); Call_Count := 2; Called_Count := 1; else Call_Count:= 0; Called_Count := 3; end if; elsif Pos.Column = HIGH2 then if Pos.Column mod 2 = 0 then Call_Neighbors (1) := (Pos.Row, Pos.Column - 1); Call_Neighbors (2) := (Pos.Row+1,Pos.Column - 1); Call_Count := 2; Called_Count := 1; else Call_Count := 0; Called_Count := 3; end if; else if Pos.Column mod 2 = 0 then Call_Neighbors (1) := (Pos.Row, Pos.Column - 1); Call_Neighbors (2) := (Pos.Row, Pos.Column + 1); Call_Neighbors (3) := (Pos.Row+1,Pos.Column - 1); Call_Neighbors (4) := (Pos.Row+1,Pos.Column + 1); Call_Count := 4; Called_Count := 1; else Call_Count := 0; Called_Count := 5; end if; end if; end if; elsif Pos.Row = HIGH1 then -- bottom border if Pos.Row mod 2 = 0 then Call_Neighbors (1) := (Pos.Row - 1, Pos.Column); if Pos.Column = LOW2 then if Pos.Column mod 2 = 0 then Call_Neighbors(2) := (Pos.Row - 1, Pos.Column + 1); Call_Neighbors (3) := (Pos.Row , Pos.Column + 1); Call_Count := 3; Called_Count := 0; else Call_Count := 1; Called_Count := 2; end if; elsif Pos.Column = HIGH2 then if Pos.Column mod 2 = 0 then Call_Neighbors(2) := (Pos.Row - 1, Pos.Column - 1); Call_Neighbors (3) := (Pos.Row, Pos.Column - 1); Call_Count := 3; Called_Count := 0; else Call_Count := 1; Called_Count := 2; end if; else if Pos.Column mod 2 = 0 then Call_Neighbors (2) := (Pos.Row - 1, Pos.Column - 1); Call_Neighbors (3) := (Pos.Row - 1, Pos.Column + 1); Call_Neighbors (4) := (Pos.Row, Pos.Column + 1); Call_Neighbors (5) := (Pos.Row, Pos.Column - 1); Call_Count := 5; Called_Count := 0; else Call_Count := 1; Called_Count := 4; end if; end if; else if Pos.Column = LOW2 then if Pos.Column mod 2 = 0 then Call_Neighbors (1) := (Pos.Row, Pos.Column + 1); Call_Neighbors (2) := (Pos.Row-1, Pos.Column + 1); Call_Count := 2; Called_Count := 1; else Call_Count:= 0; Called_Count := 3; end if; elsif Pos.Column = HIGH2 then if Pos.Column mod 2 = 0 then Call_Neighbors (1) := (Pos.Row, Pos.Column - 1); Call_Neighbors (2) := (Pos.Row-1,Pos.Column - 1); Call_Count := 2; Called_Count := 1; else Call_Count := 0; Called_Count := 3; end if; else if Pos.Column mod 2 = 0 then Call_Neighbors (1) := (Pos.Row, Pos.Column - 1); Call_Neighbors (2) := (Pos.Row, Pos.Column + 1); Call_Neighbors (3) := (Pos.Row-1,Pos.Column - 1); Call_Neighbors (4) := (Pos.Row-1,Pos.Column + 1); Call_Count := 4; Called_Count := 1; else Call_Count := 0; Called_Count := 5; end if; end if ; end if; else -- interior cell if Pos.Row mod 2 = 0 then Call_Neighbors (1) := (Pos.Row + 1, Pos.Column); Call_Neighbors (2) := (Pos.Row - 1, Pos.Column); if Pos.Column = LOW2 then if Pos.Column mod 2 = 0 then Call_Neighbors(3) := (Pos.Row + 1, Pos.Column + 1); Call_Neighbors (4) := (Pos.Row , Pos.Column + 1); Call_Neighbors(5) := (Pos.Row - 1, Pos.Column + 1); Call_Count := 5; Called_Count := 0; else Call_Count := 2; Called_Count := 3; end if; elsif Pos.Column = HIGH2 then if Pos.Column mod 2 = 0 then Call_Neighbors(3) := (Pos.Row + 1, Pos.Column - 1); Call_Neighbors (4) := (Pos.Row, Pos.Column - 1); Call_Neighbors(5) := (Pos.Row - 1, Pos.Column - 1); Call_Count := 5; Called_Count := 0; else Call_Count := 2; Called_Count := 3; end if; else if Pos.Column mod 2 = 0 then Call_Neighbors(3) := (Pos.Row + 1, Pos.Column - 1); Call_Neighbors(4) := (Pos.Row + 1, Pos.Column + 1); Call_Neighbors(5) := (Pos.Row, Pos.Column + 1); Call_Neighbors(6) := (Pos.Row, Pos.Column - 1); Call_Neighbors(7) := (Pos.Row - 1, Pos.Column + 1); Call_Neighbors(8) := (Pos.Row - 1, Pos.Column - 1); Call_Count := 8; Called_Count := 0; else Call_Count := 2; Called_Count := 6; end if; end if; else if Pos.Column = LOW2 then if Pos.Column mod 2 = 0 then Call_Neighbors (1) := (Pos.Row, Pos.Column + 1); Call_Neighbors (2) := (Pos.Row+1, Pos.Column + 1); Call_Neighbors (3) := (Pos.Row -1, Pos.Column + 1); Call_Count := 3; Called_Count := 2; else Call_Count:= 0; Called_Count := 5; end if; elsif Pos.Column = HIGH2 then if Pos.Column mod 2 = 0 then Call_Neighbors (1) := (Pos.Row, Pos.Column - 1); Call_Neighbors (2) := (Pos.Row+1,Pos.Column - 1); Call_Neighbors (3) := (Pos.Row-1, Pos.Column - 1); Call_Count := 3; Called_Count := 2; else Call_Count := 0; Called_Count := 5; end if; else if Pos.Column mod 2 = 0 then Call_Neighbors (1) := (Pos.Row, Pos.Column - 1); Call_Neighbors (2) := (Pos.Row, Pos.Column + 1); Call_Neighbors (3) := (Pos.Row + 1,Pos.Column - 1); Call_Neighbors (4) := (Pos.Row + 1,Pos.Column + 1); Call_Neighbors (5) := (Pos.Row - 1,Pos.Column - 1); Call_Neighbors (6) := (Pos.Row - 1,Pos.Column + 1); Call_Count := 6; Called_Count := 2; else Call_Count := 0; Called_Count := 8; end if; end if; end if; end if; end Find_Neighbors; task body CELL is -- The major design decision was how to prevent deadlock between cells -- that have to send information over a shared communication line. -- We decided to have all cells in even columns be the callers, and all -- cells in odd columns be the called tasks. In addition, each cell in an -- even row calls the cell immediately above and below it. Neighbors : NEIGHBOR; Call_Count : NEIGHBOR_NUMBER;-- count of neighbors to call Called_Count: NEIGHBOR_NUMBER;-- count of calls that are received from -- neighbors Count : NEIGHBOR_NUMBER;-- count of live neighbors State : BOOLEAN; -- the cell's state In_State : BOOLEAN; -- its neighbor's state Over : BOOLEAN; -- game is over Pos : COORDINATE; -- cell ID begin -- Initialization provides tasks its row and column and initial status accept Initialization (Position: COORDINATE; Status: BOOLEAN) do Pos := Position; State := Status; end Initialization; -- For a set position in the grid, find the number and identification -- of neighbors to call, as well as the number that will call this task Find_Neighbors (Pos, Neighbors, Call_Count, Called_Count); -- Receive call for printout and synchronization, and when to terminate game loop accept Output ( Done : in BOOLEAN; Status : out BOOLEAN) do Over := Done; Status := State; end; exit when Over; -- Counting the number of live neighbors Count := 0; for i in reverse 1 .. Call_Count loop Life_Grid (Neighbors(i).Row, Neighbors(i).Column).Update (State, In_State); if In_State then Count := Count + 1; end if; end loop; for i in 1 .. Called_Count loop accept Update (In_Status : in BOOLEAN; Out_Status: out BOOLEAN) do Out_Status := State; if In_Status then Count := Count + 1; end if; end; end loop; -- Determine whether the cell will be alive in the next generation State := Count = 3 or else (State and Count = 2); end loop; -- continue to the next generation exception when others => TEXT_IO.Put_Line ("Error in CELL"); end CELL; begin NULL; end Life_Game;