PROGRAM Bowling;
{Programmer:	Gregory A. Perkins
				CS 65
 Due:			25 October 1996}

{This program produces graphical output of the scores for a game of bowling.  The user
	enters the number of pins knocked down with each successive roll of the ball and 
	the program determines the score which is displayed on a graphical scorecard.}
	
{------------------------------------------------------------------------------------}

{ Program shell for automatic bowling scorer.
  Graphics routines by: John Zelle
  
  Provided routines
    DigitToChar: Converts a single digit into a character.
                 (Note: 10 is converted to 'X').  Example:
                 Ch := DigitToChar(MyDigit);
    IntToStr: Converts an integer into a string suitable for use
              with outTextXY.  Example:  
                 IntToStr(MyInt, 3, MyString);
                 outTextXY(10,100, 'The Value is: '+ MyString);
              (Note: Width is the width of the resulting string.  Blanks are
               added on the left.  If MyInt has more than Width digits, Mystring
               will expand to the length required.)
    DrawScorecard: Draw the bowling scorecard near the top of the screen. Ex:
                      DrawScoreCard;
    FrameScore: Takes an Integer representing the Frame number and another 
                representing a score.  The score is recorded as the score for
                the given frame.  Example:
                    FrameScore(ThisFrame, CurrentScore);
                (Note: Frame must be a value from 1-10 inclusive, and score should be
                       between 0 and 300.)
    FrameRoll: Fills in a roll "cell" in a given frame with the character provided.
               Frames 1 through 9 have two cells, cell 1 is in the center of the top 
               of the frame (just to the left of the inset box), cell 2 is the inset 
               box (corresponding to the recording locations of the two rolls).  Frame
               10 has 3 cells across the top. Example:
                        FrameRoll(ThisFrame, 2, 'X');
                Places an 'X' in the position for the second role in ThisFrame.
}

USES cs65;

CONST Width    = 60;   {The width (and height) of each square in scorecard}
      TopMarg  = 400;   {Distance of card from top}
      LeftMarg = 20;   {Distance of card from left side}
      TextHeight = 8;  {Size of text characters in graphics mode}
      TextWidth = 8;

{---------------------------------------------------------------------------}
FUNCTION DigitToChar(D : INTEGER) : Char;
{Converts a digit to a character (e.g. 3 becomes '3').  
 Numbers other than a single digit become an 'X' }
BEGIN
IF (D > 9) OR (D < 0) THEN
    DigitToChar := 'X'
ELSE
    DigitToChar := CHR(D+48);
END;

{---------------------------------------------------------------------------}
PROCEDURE IntToStr( X, Width:INTEGER; VAR Result:STRING);
{ Convert an Integer into a String suitable for outTextXY.  If X has
  fewer than Width digits, it is padded on the left with blanks.  If
  X has more than Width digits, Result will be whatever length is
  required to store the digits}
  
VAR Length,Digit, i, Index : INTEGER;
    tempStr : STRING;
        
BEGIN {IntToStr}
{First decode digits into tempStr}
Length := 0;
IF X = 0 THEN
    BEGIN
    Length := 1;
    tempStr := "0";
    END
ELSE
    WHILE X > 0 DO
        BEGIN
        Length := Length + 1;
        Digit := X MOD 10;
        TempStr[Length] := DigitToChar(Digit);
        X := X DIV 10;
        END; 

{Pad to appropriate width}
WHILE Length < Width DO
    BEGIN
    Length := Length + 1;
    TempStr[Length] := ' ';
    END;

{Copy into Reslt in reverse order}
Index := 1;
FOR i := Length DOWNTO 1 DO
    BEGIN
    Result[Index] := tempStr[i];
    Index := Index + 1;
    END;
 
{Set length of Result}
Result[0] := CHR(Length);
END; {IntToStr}

{---------------------------------------------------------------------------}
PROCEDURE DrawScorecard;  
VAR Square      : INTEGER;
    Xul, Xur    : INTEGER;

BEGIN
FOR Square := 0 TO 9 DO
    BEGIN
    setColor(3);                    {Use Cyan for main squares}
    Xul := Width * Square + LeftMarg;
    Xur := Xul+Width -1;
    BAR(Xul, TopMarg, Xur, TopMarg+Width-1);
    Xul := Xul + 2*Width DIV 3;
    setColor(14);           {Cells for rolls in yellow}
    BAR(Xul, TopMarg, Xur, TopMarg+Width DIV 3-1);
    IF Square = 9 THEN
        BAR(Width*9+Width DIV 3+LeftMarg, TopMarg, 
            Xur-Width Div 3, TopMarg+Width DIV 3 -1);
    END;
END;

{---------------------------------------------------------------------------}
PROCEDURE FrameScore(Frame, Score : INTEGER);
{ Set the score in given Frame to Score}

VAR X,Y : INTEGER;
    ScoreText : STRING;

BEGIN
X := (Frame-1)*Width +LeftMarg +(Width-3*TextWidth)DIV 2;
Y := TopMarg +(Width+Width DIV 4+TextHeight) DIV 2;
IntToStr(Score,3,ScoreText);
OutTextXY(X,Y, ScoreText);
END;

{---------------------------------------------------------------------------}
PROCEDURE FrameRoll(Frame, Roll : INTEGER; Value : CHAR);
{ Put Value into the cell appropriate for the Roll in the Frame}

VAR X,Y : INTEGER;
    ScoreText : STRING;
BEGIN
IF Frame = 10 THEN
        Roll := Roll - 1;
X := (Frame-1)*Width+Roll*Width DIV 3+(Width-TextWidth) DIV 2;
Y := TopMarg + (Width DIV 3 + TextHeight) DIV 2;
OutTextXY(X,Y,Value);
END;
  
{---------------------------------------------------------------------------}
PROCEDURE DisTurkey;
{Displays message of congratulations to user for three strikes in a row}

VAR Count : INTEGER;

BEGIN
	OUTTEXTXY(236, 350, '***CONGRATULATIONS***');
	
	{Make red "turkey" blink five times}
	FOR Count := 1 TO 5 DO
		BEGIN
			SetColor(4);
			OUTTEXTXY(296, 365, 'TURKEY');
			Delay(3000);
			SetColor(15);
			OUTTEXTXY(296, 365, 'TURKEY');
			Delay(3000);
		END;
		
	{Erase all remarks}
	SetColor(15);
	OUTTEXTXY(236, 350, '***CONGRATULATIONS***');
	OUTTEXTXY(296, 365, 'TURKEY');
	SetColor(0);{Return printing color to normal}
END;
{---------------------------------------------------------------------------}
PROCEDURE DisStrike;
{Displays message of congratulations to user for a Strike}

VAR Count : INTEGER;

BEGIN
	OUTTEXTXY(236, 350, '***CONGRATULATIONS***');
	
	{Make red "strike" blink five times}
	FOR Count := 1 TO 5 DO
		BEGIN
			SetColor(4);
			OUTTEXTXY(296, 365, 'STRIKE');
			Delay(3000);
			SetColor(15);
			OUTTEXTXY(296, 365, 'STRIKE');
			Delay(3000);
		END;
		
	{Erase all remarks}
	SetColor(15);
	OUTTEXTXY(236, 350, '***CONGRATULATIONS***');
	OUTTEXTXY(296, 365, 'STRIKE');
	SetColor(0);{Return printing color to normal}
END;

{---------------------------------------------------------------------------}
{ Main Program Starts Here }
 
{Declarations section}
VAR Frame, Score, Roll1, Roll2 : INTEGER;
{Spare/Strike true in previous frame - Double true when strikes in previous two frames}
VAR Spare, Strike, Double : BOOLEAN;
VAR FrameStr, RollCorrect : STRING;{Used for printing frame numbers, correcting input numbers}

{constants used for positioning of frame numbers and rolls}
CONST FrameMarg = LeftMarg + 12;
CONST Roll1Marg = LeftMarg + 255;
CONST Roll2Marg = LeftMarg + 503;

BEGIN  {Main Program}

{Variable initialization section}
Frame := 0; Score := 0; Roll1 := 0; Roll2 := 0;
Spare := FALSE; Strike := FALSE; Double := FALSE;

{Place computer in graphics mode and draw the scorecard and
	the frame/roll column headings on the screen.}
SetGraph;
DrawScoreCard;
SetColor(0);{Set color to black for all printing}
OUTTEXTXY(175, 40, 'Welcome to the Automatic Bowling Scorer!!!');
OUTTEXTXY(155, 55, '(ENTER number of pins knocked down in each frame)');
OUTTEXTXY(LeftMarg, 80, 'Frame');
OUTTEXTXY(LeftMarg, 82, '_____');
OUTTEXTXY(LeftMarg + 248, 80, '1st Ball');
OUTTEXTXY(LeftMarg + 248, 82, '_______');
OUTTEXTXY(LeftMarg + 496, 80, '2nd Ball');
OUTTEXTXY(LeftMarg + 496, 82, '_______');

{Frame Labeling Loop}
FOR Frame := 0 TO 9 DO
	BEGIN
		IntToStr(Frame + 1, 2, FrameStr);{change frame number to string for output}
		OUTTEXTXY(LeftMarg + Width * Frame + 2, TopMarg - 1, 'Frame ' + FrameStr);
	END;
	
{Main Loop}
FOR Frame := 1 TO 10 DO
	BEGIN{main loop}
		IntToStr(Frame, 2, FrameStr);{change frame number to string for output}
		OUTTEXTXY(FrameMarg, (Frame * 15) + 82, FrameStr);{print frame number}
		OUTTEXTXY(Roll1Marg, (Frame * 15) + 82, '>> ');{position cursor for Roll1 input}
		
		{Get value of Roll1 from the user}
		ReadInt(Roll1);
		
		{Test for valid rolls}
		{************ NOT COVERED IN CLASS *************}
		{****** INPUTTING INVALID NUMBERS WAS GETTING ON MY NERVES ******}
		WHILE (Roll1 > 10) DO
			BEGIN
				IntToStr(Roll1, 2, FrameStr);{Change wrong input number to 'string'}
				OUTTEXTXY(Roll1Marg, (Frame * 15) + 82, '>> ');{position cursor for correction}
				
				{Erase wrong input number}
				SetColor(15);
				OUTTEXT(FrameStr);
				SetColor(0);
								
				{Tell user that he has screwed up}
				SetColor(4);
				OUTTEXTXY(212, 350, 'NUMBER MUST BE BETWEEN 0 AND 10');
				OUTTEXTXY(256, 365, 'Hit any key to continue...');
				Pause;
				SetColor(15);
				OUTTEXTXY(212, 350, 'NUMBER MUST BE BETWEEN 0 AND 10');
				OUTTEXTXY(256, 365, 'Hit any key to continue...');
				SetColor(0);
				OUTTEXTXY(Roll1Marg, (Frame * 15) + 82, '>> ');{reposition cursor}
				
				{Get new number from user}
				ReadInt(Roll1);
			END;	
		
		{Erase pointer showing next input location}
		SetColor(15);
		OUTTEXTXY(Roll1Marg, (Frame * 15) + 82, '>> ');
		SetColor(0);
		
		{Update frames (if any) pending one roll}
		IF Double THEN
			BEGIN
				Score := Score + 20 + Roll1;
				FrameScore(Frame - 2, Score);
			END
		ELSE IF Spare THEN
			BEGIN
				Score := Score + 10 + Roll1;
				FrameScore(Frame - 1, Score);
			END;
		
		{Determine whether a second roll is required}
		IF ((Roll1 = 10) AND (Frame = 10)) THEN
			BEGIN
				FrameRoll(Frame, 1, 'X');
				
				{Test if Displaying Turkey or Strike Procedure is necessary}
				IF Double THEN
					BEGIN
						DisTurkey;
					END
				ELSE
					BEGIN
						DisStrike;
					END;

			END
		ELSE IF (Roll1 = 10) THEN
			BEGIN
				FrameRoll(Frame, 2, 'X');
				
				{Test if Displaying Turkey or Strike Procedure is necessary}
				IF Double THEN
					BEGIN
						DisTurkey;
					END
				ELSE
					BEGIN
						DisStrike;
					END;
			END
		ELSE
			BEGIN
				FrameRoll(Frame, 1, DigitToChar(Roll1));
				OUTTEXTXY(Roll2Marg, (Frame * 15) + 82, '>> ');{position cursor for Roll2 input}
				
				{Get value of Roll2 from user}
				ReadInt(Roll2);
				
				{Test for valid rolls}
				{************ NOT COVERED IN CLASS *************}
				{****** INPUTTING INVALID NUMBERS WAS GETTING ON MY NERVES ******}
				WHILE (Roll1 + Roll2 > 10) DO
					BEGIN
						IntToStr(Roll2, 1, FrameStr);{Change wrong input number to 'string'}
						OUTTEXTXY(Roll2Marg, (Frame * 15) + 82, '>> ');{position cursor for correction}
				
						{Erase wrong input number}
						SetColor(15);
						OUTTEXT(FrameStr);
						SetColor(0);
														
						{Tell user that he has screwed up}
						SetColor(4);
						OUTTEXTXY(183, 350, 'BALL 1 AND BALL 2 CANNOT TOTAL MORE THAN 10');
						OUTTEXTXY(256, 365, 'Hit any key to continue...');
						Pause;
						SetColor(15);
						OUTTEXTXY(183, 350, 'BALL 1 AND BALL 2 CANNOT TOTAL MORE THAN 10');
						OUTTEXTXY(256, 365, 'Hit any key to continue...');
						SetColor(0);
						OUTTEXTXY(Roll2Marg, (Frame * 15) + 82, '>> ');{reposition cursor}
				
						{Get new number from user}
						ReadInt(Roll2);
					END;	

				{Erase pointer showing next input location}
				SetColor(15);
				OUTTEXTXY(Roll2Marg, (Frame * 15) + 82, '>> ');
				SetColor(0);
				
				{Score any frames (if any) pending 2 rolls}
				IF Strike THEN
					BEGIN
						Score := Score + 10 + Roll1 + Roll2;
						FrameScore(Frame - 1, Score);
					END;
					
				{Score this frame (if possible)}
				IF ((Roll1 < 10) AND (Roll1 + Roll2 = 10)) THEN
					BEGIN
						FrameRoll(Frame, 2, '/');
					END
				ELSE
					BEGIN
						FrameRoll(Frame, 2, DigitToChar(Roll2));
						Score := Score + Roll1 + Roll2;
						FrameScore(Frame, Score);
					END;
			END;{Second Roll }
				
		{Compute new values of Double, Strike, and Spare}
		Double := (Strike AND (Roll1 = 10));
		Strike := (Roll1 = 10);
		Spare := ((Roll1 < 10) AND (Roll1 + Roll2 = 10));
				
	END;{of main loop}

{Clean up the tenth frame (if necessary)}
IF Strike THEN
	BEGIN
		OUTTEXTXY(LeftMarg, (Frame * 15) + 97, 'Extra Balls:');
		OUTTEXTXY(Roll1Marg, (Frame * 15) + 97, '>> ');{Position cursor for extra Roll1 input}
		
		{Get value of extra Roll1 from user and record in correct cell}
		ReadInt(Roll1);
		
		{Test for valid rolls}
		{************ NOT COVERED IN CLASS *************}
		{****** INPUTTING INVALID NUMBERS WAS GETTING ON MY NERVES ******}
		WHILE (Roll1 > 10) DO
			BEGIN
				IntToStr(Roll1, 2, FrameStr);{Change wrong input number to 'string'}
				OUTTEXTXY(Roll1Marg, (Frame * 15) + 97, '>> ');{position cursor for correction}
				
				{Erase wrong input number}
				SetColor(15);
				OUTTEXT(FrameStr);
				SetColor(0);
												
				{Tell user that he has screwed up}
				SetColor(4);
				OUTTEXTXY(212, 350, 'NUMBER MUST BE BETWEEN 0 AND 10');
				OUTTEXTXY(256, 365, 'Hit any key to continue...');
				Pause;
				SetColor(15);
				OUTTEXTXY(212, 350, 'NUMBER MUST BE BETWEEN 0 AND 10');
				OUTTEXTXY(256, 365, 'Hit any key to continue...');
				SetColor(0);
				OUTTEXTXY(Roll1Marg, (Frame * 15) + 97, '>> ');{reposition cursor}
				
				{Get new number from user}
				ReadInt(Roll1);
			END;	

		{Erase the pointer}
		SetColor(15);
		OUTTEXTXY(Roll1Marg, (Frame * 15) + 97, '>> ');
		SetColor(0);
		
		FrameRoll(10, 2, DigitToChar(Roll1));
		
		{Display Strike Procedure if appropriate}
		IF ((Roll1 = 10) AND (NOT Double)) THEN
			DisStrike;

		IF Double THEN
			BEGIN
				Score := Score + 20 + Roll1;
				FrameScore(9, Score);
				
				{Test to see if displaying the Turkey Procedure is necessary}
				IF (Roll1 = 10) THEN
					DisTurkey;
			END;
		
		OUTTEXTXY(Roll2Marg, (Frame * 15) + 97, '>> ');{Position cursor for extra Roll2 input}
		
		{Get value of extra Roll2 from user and record in correct cell}
		ReadInt(Roll2);
		
		{Test for valid rolls when strike rolled in second cell of tenth frame}
		{************ NOT COVERED IN CLASS *************}
		{****** INPUTTING INVALID NUMBERS WAS GETTING ON MY NERVES ******}
		WHILE ((Roll1 = 10) AND (Roll2 > 10)) DO
			BEGIN
				IntToStr(Roll2, 2, FrameStr);{Change wrong input number to 'string'}
				OUTTEXTXY(Roll2Marg, (Frame * 15) + 97, '>> ');{position cursor for correction}
				
				{Erase wrong input number}
				SetColor(15);
				OUTTEXT(FrameStr);
				SetColor(0);
												
				{Tell user that he has screwed up}
				SetColor(4);
				OUTTEXTXY(212, 350, 'NUMBER MUST BE BETWEEN 0 AND 10');
				OUTTEXTXY(256, 365, 'Hit any key to continue...');
				Pause;
				SetColor(15);
				OUTTEXTXY(212, 350, 'NUMBER MUST BE BETWEEN 0 AND 10');
				OUTTEXTXY(256, 365, 'Hit any key to continue...');
				SetColor(0);
				OUTTEXTXY(Roll2Marg, (Frame * 15) + 97, '>> ');{reposition cursor}
				
				{Get new number from user}
				ReadInt(Roll2);
			END;	

		
		{Test for valid rolls for other than perfect game}
		{************ NOT COVERED IN CLASS *************}
		{****** INPUTTING INVALID NUMBERS WAS GETTING ON MY NERVES ******}
		WHILE ((Roll1 < 10) AND (Roll1 + Roll2 > 10)) DO
			BEGIN
				IntToStr(Roll2, 1, FrameStr);{Change wrong input number to 'string'}
				OUTTEXTXY(Roll2Marg, (Frame * 15) + 97, '>> ');{position cursor for correction}
				
				{Erase wrong input number}
				SetColor(15);
				OUTTEXT(FrameStr);
				SetColor(0);
				
				{Tell user that he has screwed up}
				SetColor(4);
				OUTTEXTXY(183, 350, 'BALL 1 AND BALL 2 CANNOT TOTAL MORE THAN 10');
				OUTTEXTXY(256, 365, 'Hit any key to continue...');
				Pause;
				SetColor(15);
				OUTTEXTXY(183, 350, 'BALL 1 AND BALL 2 CANNOT TOTAL MORE THAN 10');
				OUTTEXTXY(256, 365, 'Hit any key to continue...');
				SetColor(0);
				OUTTEXTXY(Roll2Marg, (Frame * 15) + 97, '>> ');{reposition cursor}
				
				{Get new number from user}
				ReadInt(Roll2);
			END;	

		{Erase the pointer}
		SetColor(15);
		OUTTEXTXY(Roll2Marg, (Frame * 15) + 97, '>> ');
		SetColor(0);
		
		IF ((Roll1 < 10) AND (Roll1 + Roll2 = 10)) THEN
			BEGIN
				FrameRoll(10, 3, '/');
			END
		ELSE
			BEGIN
				FrameRoll(10, 3, DigitToChar(Roll2));
			END;
			
		{Test to see if displaying the Turkey or Strike Procedure is necessary}
		IF ((Roll1 = 10) AND (Roll2 = 10)) THEN
			BEGIN
				DisTurkey;
			END;
						
		Score := Score + 10 + Roll1 + Roll2;
		FrameScore(10, Score);
	END
ELSE IF Spare THEN
	BEGIN
		OUTTEXTXY(LeftMarg, (Frame * 15) + 97, 'Extra Ball:');
		OUTTEXTXY(Roll1Marg, (Frame * 15) + 97, '>> ');{Position cursor for extra Roll1 input}
		
		{Get value of extra Roll1 for user and record/score the frame}
		ReadInt(Roll1);
		
		{Test for valid rolls}
		{************ NOT COVERED IN CLASS *************}
		{****** INPUTTING INVALID NUMBERS WAS GETTING ON MY NERVES ******}
		WHILE (Roll1 > 10) DO
			BEGIN
				IntToStr(Roll1, 2, FrameStr);{Change wrong input number to 'string'}
				OUTTEXTXY(Roll1Marg, (Frame * 15) + 97, '>> ');{position cursor for correction}
				
				{Erase wrong input number}
				SetColor(15);
				OUTTEXT(FrameStr);
				SetColor(0);
												
				{Tell user that he has screwed up}
				SetColor(4);
				OUTTEXTXY(212, 350, 'NUMBER MUST BE BETWEEN 0 AND 10');
				OUTTEXTXY(256, 365, 'Hit any key to continue...');
				Pause;
				SetColor(15);
				OUTTEXTXY(212, 350, 'NUMBER MUST BE BETWEEN 0 AND 10');
				OUTTEXTXY(256, 365, 'Hit any key to continue...');
				SetColor(0);
				OUTTEXTXY(Roll1Marg, (Frame * 15) + 97, '>> ');{reposition cursor}
				
				{Get new number from user}
				ReadInt(Roll1);
			END;	

		{Erase the pointer}
		SetColor(15);
		OUTTEXTXY(Roll1Marg, (Frame * 15) + 97, '>> ');
		SetColor(0);
		
		FrameRoll(10, 3, DigitToChar(Roll1));
		Score := Score + 10 + Roll1;
		FrameScore(10, Score);
		
		{Display 'STRIKE' remarks if necessary}
		IF (Roll1 = 10) THEN
			DisStrike;
			
	END;
	
Pause;

END.