PROGRAM Artillery_Battle ;  { Pascal/M version }

 { *** WRITTEN BY      Jim Bearden
                       Cancer Center, University of Hawaii
                       1236 Lauhala Street
                       Honolulu, Hawaii 96813

   *** DATE WRITTEN    December, 1981

   *** PROGRAM SUMMARY:
         This program simulates an artillery battle between two players.
       The initial setup (ground elevation on each side, locations of
       bunkers, height of the central "hill" or "valley", and wind
       direction and velocity) is generated at random for each game.
       Each player then enters the angle (in degrees above the horizontal)
       and velocity (in meters per second) of his shot. Flight of the
       projectile and its point of impact are displayed, as long as it
       remains on the screen. This program uses only ASCII characters and
       cursor positioning (no graphics capability required), so it should
       be usable on any CRT terminal with an addressable cursor (presently
       set up for a 24 x 80 screen size, but may be adapted to other sizes
       by changing constants as shown).
 }

 {$D- (Disable "Debug" option for increased speed) }

 CONST
   Grnd_sym  = '-' ; { Character used to represent ground surface in profile }
   Burst_sym = '*' ; { Represents point of impact on ground }
   Air_sym   = '+' ; { Represents projectile in flight }
   Max_X     =  79 ; { Number of screen columns - 1 }
   Max_Y     =  20 ; { Number of screen lines - 4 }
   Scale     = 100 ; { Number of meters represented by each screen unit }
   Max_wind  =  25 ; { Maximum allowed wind velocity, in meters/sec }
   Side_1    =  25 ; { Width of level ground section on player 1's side }
   Side_2    =  25 ; { Same, player 2's side }
   Center    =  30 ; { Width of center ("hill" or "valley") section }
     { Sum of (Side_1 + Side_2 + Center) must equal width of screen }
   Acc_g     = 9.8 ; { Acceleration of normal earth gravity, in meters/sec }
   Bell      =   7 ; { ASCII equivalent of "bell" sound }

 VAR
   X_pos_1, X_pos_2,   { Locations (on X-axis) of bunkers of players 1 and 2 }
   Level_1, Level_2,   { Heights of level ground sections on each side }
   Center_ht       : 0..Max_X ;
   Wind_vel        : -Max_wind..Max_wind ;
   Ground          : ARRAY [0..Max_X] OF Integer ;
   Direct_hit      : ARRAY [1..2] OF Boolean ;
   Rnd_num         : Real ;
   Answer          : Char ;
   Quit            : Boolean ;

PROCEDURE Set_Up_Game ;

 VAR
   X, Y : 0..Max_X ;

BEGIN { Set_Up_Game }

 { Set up initial conditions from random-number generator }
 Level_1 := Round (Max_Y * Random (0) / 2) ;
 Level_2 := Round (Max_Y * Random (0) / 2) ;
 Center_ht := Round (Max_Y * Random (0)) ;
 X_pos_1 := Round ((Side_1 - 3) * Random (0) + 1) ;
 X_pos_2 := Round ((Side_2 - 3) * Random (0) + Max_X - Side_2 + 1) ;
 Wind_vel := Round (2 * Max_wind * Random (0) - Max_wind) ;

 { Display initial layout and initialize "Ground" values }
 Conact (0) ;
 FOR X := 0 TO (Side_1 - 1) DO
   BEGIN
     Y := Level_1 ;
     GotoXY (X, (Max_Y - Y)) ;
     Write (Grnd_sym) ;
     Ground [X] := Y ;
   END ;
 FOR X := 0 TO (Center DIV 2 - 1) DO
   BEGIN
     Y := Round (Level_1 + (Center_ht - Level_1) * (X + 1) / (Center / 2)) ;
     GotoXY ((X + Side_1), (Max_Y - Y)) ;
     Write (Grnd_sym) ;
     Ground [X + Side_1] := Y ;
   END ;
 FOR X := 0 TO (Center DIV 2 - 1) DO
   BEGIN
     Y := Center_ht - Round ((Center_ht - Level_2) * (X + 1) / (Center / 2)) ;
     GotoXY ((X + Side_1 + Center DIV 2), (Max_Y - Y)) ;
     Write (Grnd_sym) ;
     Ground [X + Side_1 + Center DIV 2] := Y ;
   END ;
 FOR X := 0 TO (Side_2 - 1) DO
   BEGIN
     Y := Level_2 ;
     GotoXY ((X + Side_1 + Center), (Max_Y - Y)) ;
     Write (Grnd_sym) ;
     Ground [X + Side_1 + Center] := Y ;
   END ;

 { Show location of both players' bunkers }
 GotoXY (X_pos_1 - 1, (Max_Y - Ground [X_pos_1])) ;
 Write ('[1]') ;
 GotoXY (X_pos_2 - 1, (Max_Y - Ground [X_pos_2])) ;
 Write ('[2]') ;

 GotoXY (0, (Max_Y + 2)) ;
 Conact (1) ;
 Write ('Wind is to the ') ;
 IF Wind_vel <= 0 THEN
   Write ('LEFT ')
 ELSE
   Write ('RIGHT ') ;
 Write ('at ', Abs (Wind_vel), ' meters/sec; ') ;
 Write ('each bar (-) is 100 meters.') ;

 GotoXY (0, Max_Y + 3) ;
 Conact (1) ;
 Write ('When prompted, enter angle (degrees) and velocity (meters/sec)') ;
 Write (', or 0,0 to quit.') ;

 GotoXY (0, (Max_Y - Level_1 + 1)) ;
 Write ('Player #1: ') ;
 GotoXY ((Side_1 + Center), (Max_Y - Level_2 + 1)) ;
 Write ('Player #2: ') ;

 FOR X := 1 TO 2 DO
   Direct_hit [X] := False ;
 Quit := False ;

END ; { Set_Up_Game }


PROCEDURE Fire (Player : Integer) ;

 CONST
   Spaces    = 11 ;
   Pi        = 3.14159 ;
   Time_int  = 100 ;

 VAR
   Angle, Init_vel,
   Last_X, Last_Y,
   Next_X, Next_Y,
   X_vel, Y_vel    : Real ;
   N, Loc_X, Loc_Y : Integer ;
   Hit, On_screen  : Boolean ;

FUNCTION Wind_Fac (X_pos, Y_pos : Real) : Real ;

 CONST
   Wind_pct  = 0.1 ; { "Coupling factor" between wind and projectile }

 VAR
   Shielded  : Boolean ;

BEGIN { Wind_Fac }

 IF Wind_vel > 0 THEN
   IF Center_ht > Level_1 THEN
     Shielded := (X_pos > (Side_1 + Center DIV 2 + 1)) AND (Y_pos < Center_ht)
   ELSE
     Shielded := (X_pos > Side_1) AND (Y_pos < Level_1)
 ELSE
   IF Center_ht > Level_2 THEN
     Shielded := (X_pos < (Side_1 + Center DIV 2)) AND (Y_pos < Center_ht)
   ELSE
     Shielded := (X_pos < (Side_1 + Center)) AND (Y_pos < Level_2) ;

 IF Shielded THEN
   Wind_Fac := 0
 ELSE
   Wind_Fac := Wind_vel * Wind_pct ;

END { Wind_Fac } ;

BEGIN { Fire }

 IF Player = 1 THEN
   GotoXY (Spaces, (Max_Y - Level_1 + 1))
 ELSE
   GotoXY ((Side_1 + Center + Spaces), (Max_Y - Level_2 + 1)) ;
 Read (Angle, Init_vel) ;

 { Routine for early termination of game by either player }
 IF (Angle <= 0) OR (Init_vel <= 0) THEN
   BEGIN
     Quit := True ;
     Exit (Fire) ;
   END ;

 { Set up zero-time co-ordinates and velocities }
 Angle := Angle * Pi / 180 ; { Convert degrees to radians }
 X_vel := Init_vel * Cos (Angle) ;
 IF Player = 2 THEN
   X_vel := -X_vel ;
 Y_vel := Init_vel * Sin (Angle) ;
 IF Player = 1 THEN
   BEGIN
     Last_X := X_pos_1 ;
     Last_Y := Ground [X_pos_1] ;
   END
 ELSE
   BEGIN
     Last_X := X_pos_2 ;
     Last_Y := Ground [X_pos_2] ;
   END ;
 Hit := False ;
 On_screen := False ;

 REPEAT

   { Compute velocities and positions after next second of travel }
   X_vel := X_vel + Wind_Fac (Last_X, Last_Y) ;
   Y_vel := Y_vel - Acc_g ;
   IF On_screen THEN { Erase last symbol printed during air travel }
     BEGIN
       GotoXY (Loc_X, (Max_Y - Loc_Y)) ;
       Write (' ') ;
     END ;
   Next_X := Last_X + (X_vel / Scale) ;
   Loc_X := Round (Next_X) ;
   Next_Y := Last_Y + (Y_vel / Scale) ;
   Loc_Y := Round (Next_Y) ;

   IF (Loc_Y < 0) AND NOT (Loc_X IN [0..Max_X]) THEN { Hit ground off screen }
     BEGIN
       Hit := True ;
       Write (Chr (Bell)) ;
     END
   ELSE IF Loc_X IN [0..Max_X] THEN
     IF Loc_Y <= Ground [Loc_X] THEN { Hit ground on screen }
       BEGIN
         Loc_X := Round (Last_X + (Next_X - Last_X) / (Next_Y - Last_Y)) ;
         Hit := True ;
         Write (Chr (Bell)) ;
         IF (Abs (Loc_X - X_pos_1)) <= 1 THEN
           Direct_hit [1] := True
         ELSE IF (Abs (Loc_X - X_pos_2)) <= 1 THEN
           Direct_hit [2] := True
         ELSE
           BEGIN
             GotoXY (Loc_X, (Max_Y - Ground [Loc_X])) ;
             Write (Burst_sym) ;
           END ;
       END
     ELSE { Still in flight above ground }
       BEGIN
         On_screen := Loc_Y IN [0..Max_Y] ;
         IF On_screen THEN
           BEGIN
             GotoXY (Loc_X, (Max_Y - Loc_Y)) ;
             Write (Air_sym) ;
           END
         ELSE
           GotoXY (Loc_X, 0) ;
         FOR N := 1 TO Time_int DO { Nothing } ;
           { Time delay for display }
       END
   ELSE
     BEGIN
       On_screen := False ;
       GotoXY (0, (Max_Y + 3)) ;
     END ;

   { Update co-ordinates for next calculation }
   Last_X := Next_X ;
   Last_Y := Next_Y ;

 UNTIL Hit ;

END ; { Fire }


PROCEDURE Show_Hit ;

 VAR
   N : Integer ;

BEGIN { Show_Hit }

 IF Direct_hit [1] THEN
   GotoXY ((X_pos_1 - 1), (Max_Y - Level_1))
 ELSE
   GotoXY ((X_pos_2 - 1), (Max_Y - Level_2)) ;
 FOR N := 1 TO 3 DO
   Write (Burst_sym) ;

 IF Direct_hit [1] THEN
   GotoXY (0, (Max_Y - Level_1 + 1))
 ELSE
   GotoXY ((Side_1 + Center), (Max_Y - Level_2 + 1)) ;
 Conact (1) ;
 Writeln ('BLEEP!!! Wiped out!!!') ;

 FOR N := 1 TO 200 DO
   Write (Chr (Bell)) ;

END ; { Show_Hit }


BEGIN { Main program }

 Conact (0) ;
 Writeln ('Welcome to ARTILLERY BATTLE') ;
 Writeln ;

 { Routine to randomize pseudo-random-number generator }
 Write ('Press any key to start the game: ') ;
 WHILE Filebusy (Keyboard) DO
   Rnd_num := Random (0) ;
 Read (Keyboard, Answer) ;

 REPEAT

   Set_Up_Game ;

   REPEAT
     Fire (1) ;
     IF NOT (Direct_hit [1] OR Direct_hit [2] OR Quit) THEN
       Fire (2) ;
   UNTIL (Direct_hit [1] OR Direct_hit [2] OR Quit) ;

   IF NOT Quit THEN
     Show_hit ;

   GotoXY (0, (Max_Y + 2)) ;
   Conact (1) ;
   GotoXY (0, (Max_Y + 3)) ;
   Conact (1) ;
   Write ('Another game (Y/n) ? ') ;
   Read (Answer) ;

 UNTIL Answer IN ['N','n'] ;

END . { Main program }