PROGRAM Artillery_Battle ;  { Pascal/Z Version }

{$C-, F-, M-, L-  (Turn off checking and listing options for better speed) }

  { *** 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 by the pseudo-random-number
	procedure, depending on the "seed" number entered at the beginning.
	Each player then enters the angle (in degrees above the horizontal)
	and velocity (in meters per second) of his shot (NOTE that because
	of the console I/O requirements of Pascal/Z, these two numbers MUST
	be separated by a space, not a comma, and followed by <Return>).
	Flight and impact point are displayed for the projectile, 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). You might also try changing the
	scale factor, maximum wind velocity, or wind "coupling factor"
	(its effect on the projectile) to get some other interesting
	variations on the game.
	  Note that this version was named "ARTIL/Z" to distinguish it from
	the other version (ARTIL/M) in Pascal/M. Unfortunately, the Pascal/Z
	compiler, assembler, and/or linker will not accept a large number of
	perfectly legal CP/M file names, including that one. Rename it to 
	plain "ARTIL" before trying to compile it.
  }




  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 code for "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 ;
    Rand_num	    : Integer ;
    Answer	    : Char ;
    Quit	    : Boolean ;


{$IGOTOXY.PSP }   { PROCEDURE GotoXY (X_pos, Y_pos : Integer) }


{$ICONACT.PSP }   { PROCEDURE Conact (Action : Integer) }


{$IRANDOM.PSP }   { FUNCTION Random (VAR Seed : Integer) : Real }


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 (Rand_num) / 2) ;
  Level_2 := Round (Max_Y * Random (Rand_num) / 2) ;
  Center_ht := Round (Max_Y * Random (Rand_num)) ;
  X_pos_1 := Round ((Side_1 - 3) * Random (Rand_num) + 1) ;
  X_pos_2 := Round ((Side_2 - 3) * Random (Rand_num) + Max_X - Side_2 + 1) ;
  Wind_vel := Round (2 * Max_wind * Random (Rand_num) - 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) : 2, ' meters/sec; ') ;
  Write ('each bar (-) is ', Scale : 3, ' 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) ;

  LABEL
    0 ;

  CONST
    Spaces    = 11 ;
    Pi	      = 3.14159 ;
    Half_cir  = 180 ; { Number of degrees equal to "Pi" radians }
    Time_int  = 100 ;

  VAR
    Last_X, Last_Y,
    Next_X, Next_Y,
    X_vel, Y_vel,
    Angle, Init_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 ;
      Goto 0 ;
    END ;

  { Set up zero-time co-ordinates and velocities }
  Angle := Angle * (Pi / Half_cir) ; { 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 ;
  
0: { Location to jump to in order to exit procedure }

END ; { Fire }


PROCEDURE Show_Hit ;

  CONST
    End_loop = 600 ;  { Used in timing loop for longer "bell" sound }

  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) ;
  Write ('BLEEP!!! Wiped out!!!') ;

  FOR N := 1 TO End_loop DO
    Write (Chr (Bell)) ;

END ; { Show_Hit }


BEGIN { Main program }

  Conact (0) ;
  Writeln ('Welcome to ARTILLERY BATTLE') ;
  Writeln ;

  { Initialize pseudo-random-number generator }
  Write ('Enter any number to start the game: ') ;
  Read (Rand_num) ;

  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 }
 (1) ;
    Write ('Another game (Y/n) ? /ÍJ*y§ÈÕ"P/ÍB.ÍÌ*Á*ùÍI."ùz³È*:R/ƒåg"Í¶'á"Í«É*ùåÍ*Ñ"ù*P/ÅÍ‰.ÁÍÌ*Éx2S/y2R/:‘<6 #=ÂÙ*++"N/É*P/Í*"P/É*P/åÍ*Ñ*ùyÍ6."
