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.