' BOWLING.BAS
' This program uses random numbers to simulate a 10-frame bowling game.

' declare subprograms and function

DECLARE SUB ScoreCard ()
DECLARE SUB Pause ()
DECLARE FUNCTION GetRandBowl% (values%)
DECLARE SUB ShowBowl (value%)
DECLARE SUB DrawPins (pins%)
DECLARE SUB ProcessSpare (extraBall%, offset%)
DECLARE SUB TenthFrameStrike ()
DECLARE SUB TenthFrameSpare ()

' declare global variables and constants

COMMON SHARED total%, strike%, spare%, score$
CONST FRAMEROW% = 3: CONST SCOREROW% = 5
CONST FALSE% = 0, TRUE% = NOT FALSE%

RANDOMIZE TIMER      ' seed random number generator with system clock
score$ = "###"       ' template for displaying total% with PRINT USING

CLS

ScoreCard            ' call ScoreCard to display frames and gutters
DrawPins 10          ' set a fresh rack of 10 pins to start game

FOR i% = 1 TO 10     ' for each of the 10 frames...
    Pause            '   pause the action
    DrawPins 10      '   set a fresh rack of 10 pins

    firstBowl% = GetRandBowl%(10)  ' get random number for first ball
    strikeDelay% = TRUE%           ' set "strike" flag to true

    IF (firstBowl% = 10) THEN      ' if first ball is a strike...
        ShowBowl 10                    ' show a throw that gets 10 pins
        DrawPins 0                     ' show an empty lane
        LOCATE FRAMEROW%, 3 + ((i% - 1) * 6)  ' use i% to locate frame
        PRINT "X"                      ' ...and print "X" for strike
        pinsDown% = 10                 ' set pinsDown% count to 10
        strike% = strike% + 1          ' increment the number of strikes
        noStrikes% = FALSE%            ' set "no strike" flag to false
        
        ' if 3 strikes have been bowled, set flag to process the first
        IF (strike% = 3) THEN strikeDelay% = TRUE% ELSE strikeDelay% = FALSE%
                                  
        ' if working on a spare, calculate score for last frame
        IF (spare%) THEN ProcessSpare firstBowl%, i%

    ELSE                           ' if first throw was not a strike...
        ShowBowl firstBowl%            ' show an appropriate throw
        DrawPins 10 - firstBowl%       ' draw the remaining pins
        LOCATE FRAMEROW%, 2 + ((i% - 1) * 6)  ' use i% to locate frame
        PRINT firstBowl%               ' ...and display first ball

        ' if working on a spare, calculate score for last frame
        IF (spare%) THEN ProcessSpare firstBowl%, i%

        Pause                          ' pause the action

        ' get random number for second ball
        secondBowl% = GetRandBowl%(10 - firstBowl%)
        ShowBowl firstBowl% + secondBowl%           ' show the ball
        DrawPins 10 - (firstBowl% + secondBowl%)    ' draw remainder

        pinsDown% = firstBowl% + secondBowl%        ' add up pins
        noStrikes% = TRUE%             ' set "no strike" flag to true
        IF (pinsDown% = 10) THEN       ' if two balls make a spare...
            LOCATE FRAMEROW%, 5 + ((i% - 1) * 6)    ' locate frame
            PRINT "/"                      ' ...and print "/" for spare
            spare% = TRUE%                 ' set "spare" flag to true
        ELSE                           ' if not a spare...
            LOCATE FRAMEROW%, 4 + ((i% - 1) * 6)    ' locate frame
            PRINT secondBowl%              ' ...and display second ball
        END IF
    END IF

    ' process outstanding strikes if bonus balls are available

    IF (strike% >= 1) AND (strikeDelay%) THEN

        IF (strike% = 1) THEN      ' if single strike
            total% = total% + 10 + firstBowl% + secondBowl%
            LOCATE SCOREROW%, 3 + ((i% - 2) * 6) ' locate last frame
            PRINT USING score$; total%           ' print total score
            strikeDelay% = FALSE%      ' set "strike" flag to false
        END IF

        IF (strike% = 2) OR (strike% = 3) THEN  ' if double or triple
            total% = total% + 10 + 10 + firstBowl%  ' total pins
            LOCATE SCOREROW%, 3 + ((i% - 3) * 6)    ' move 2 frames back
            PRINT USING score$; total%              ' print total score
            IF (noStrikes%) THEN   ' if all bonus balls available
                total% = total% + 10 + firstBowl% + secondBowl%
                LOCATE SCOREROW%, 3 + ((i% - 2) * 6)    ' get last frame
                PRINT USING score$; total%              ' print total score
                strike% = 0                             ' set number of
            END IF                                      '   strikes to zero
        END IF

        strike% = strike% - 1      ' decrement number of strikes
        IF (strike% < 0) THEN strike% = FALSE%  ' don't go below zero
    END IF

    IF (pinsDown% < 10) AND (NOT strike%) THEN  ' calculate pin total
        total% = total% + pinsDown%             '   for open frame
        LOCATE SCOREROW%, 3 + ((i% - 1) * 6)    '   and display score
        PRINT USING score$; total%
    END IF

NEXT i%

IF (strike%) THEN TenthFrameStrike ' if a strike in the 10th frame

IF (spare%) THEN TenthFrameSpare   ' if a spare in the 10th frame

END                                ' end main program

SUB DrawPins (num%)

' DrawPins displays the number of pins specified by num%.
'   CHR$(173) is an upside-down exclamation mark from the
'   IBM extended character set used to represent a pin.

COLOR 14         ' set color to yellow

LOCATE 21, 70    ' this is the last pin to be knocked down
IF (num% = 10) THEN PRINT CHR$(173) ELSE PRINT " "

LOCATE 20, 68
IF (num% >= 9) THEN PRINT CHR$(173) ELSE PRINT " "

LOCATE 19, 66
IF (num% >= 8) THEN PRINT CHR$(173) ELSE PRINT " "

LOCATE 19, 70
IF (num% >= 7) THEN PRINT CHR$(173) ELSE PRINT " "

LOCATE 18, 68
IF (num% >= 6) THEN PRINT CHR$(173) ELSE PRINT " "

LOCATE 18, 64
IF (num% >= 5) THEN PRINT CHR$(173) ELSE PRINT " "

LOCATE 17, 66
IF (num% >= 4) THEN PRINT CHR$(173) ELSE PRINT " "

LOCATE 16, 68
IF (num% >= 3) THEN PRINT CHR$(173) ELSE PRINT " "

LOCATE 17, 70
IF (num% >= 2) THEN PRINT CHR$(173) ELSE PRINT " "

LOCATE 15, 70    ' this is the first pin to be knocked down
IF (num% >= 1) THEN PRINT CHR$(173) ELSE PRINT " "

COLOR 7          ' set color back to white

END SUB

FUNCTION GetRandBowl% (values%)

' GetRandBowl% returns a random number between 1 and values% to
'   the calling procedure.

GetRandBowl% = INT(RND * (values% + 1))

END FUNCTION

SUB Pause
 
' Pause returns the ball to the top of the lane and pauses the action.

FOR ballPos% = 68 TO 3 STEP -1          ' return ball to top of lane
    SOUND 150, .1                       ' play appropriate sound
    LOCATE 12, ballPos%: PRINT CHR$(2)  ' ball is ASCII code number 2
    SOUND 160, .1
    LOCATE 12, ballPos% + 1: PRINT " "
    SOUND 1170, .1
NEXT ballPos%

LOCATE 8, 1
COLOR 14, 1                            ' set color to yellow-on-blue
INPUT "Press Enter to bowl ", dummy$   ' pause the action
COLOR 7, 0                             ' return color to defaults

LOCATE 12, 3: PRINT " "                ' hide resting place of ball

END SUB

SUB ProcessSpare (extraBall%, offset%)

' ProcessSpare calculates the score for the preceeding frame when
'   the extra ball is available.  Offset% is the frame location.

total% = total% + 10 + extraBall%          ' calculate frame score
LOCATE SCOREROW%, 3 + ((offset% - 2) * 6)  ' use offset% to get frame
PRINT USING score$; total%                 ' display the total
spare% = FALSE%                            ' set "spare" switch to false

END SUB

SUB ScoreCard

' ScoreCard displays the bowling scorecard and the alley gutters.

COLOR 3                 ' set foreground color to cyan

PRINT "   1     2     3     4     5     6     7     8     9     10"
PRINT STRING$(63, "-")
PRINT "|     |     |     |     |     |     |     |     |     |       |"
PRINT "|     |     |     |     |     |     |     |     |     |       |"
PRINT "|     |     |     |     |     |     |     |     |     |       |"
PRINT STRING$(63, "-")

LOCATE 13, 5
PRINT STRING$(66, "-")
LOCATE 23, 5
PRINT STRING$(66, "-")

COLOR 7                 ' set foreground color to white

END SUB

SUB ShowBowl (pins%)

' ShowBowl shows the ball rolling down the lane through the pins
'   with appropriate sounds.  The pins% parameter represents the
'   number of pins that should fall and is used to send the ball
'   in the right direction.

LOCATE 8, 1                     ' cover up the "Press Enter" message
PRINT SPACE$(20)

value% = (pins% + 1) \ 2        ' set target range
IF (value% < 3) AND (value% >= 0) THEN value% = value% + 1
row% = 18                       ' set starting row

FOR i% = 2 TO 68 STEP 10        ' roll ball down lane
    FOR j% = 1 TO 10
        SOUND 63, .2            ' play appropriate sound
        LOCATE row%, i% - 1 + j%: PRINT " "   ' cover up tracks
       
        IF (i% > 54) AND (row% <= 18) THEN    ' change sound when in
            FOR k% = 1 TO 5                   '   the pins
                note% = INT(RND(1) * 150): SOUND 50 + note%, .1
            NEXT k%
        END IF
       
        IF (i% > 61) AND (row% >= 19) AND (row <= 21) THEN
            FOR k% = 1 TO 5
                note% = INT(RND(1) * 150): SOUND 50 + note%, .1
            NEXT k%
        END IF

        SOUND 68, .2
        LOCATE row%, i% + j%: PRINT CHR$(2)   ' display ball
        SOUND 65, .2
    NEXT j%

    LOCATE row%, i% - 1 + j%: PRINT " "       ' cover up tracks
    IF (value% >= 5) THEN row% = row% - 1
    row% = row% + 1: value% = value% + 1      ' move to next row
NEXT i%

END SUB

SUB TenthFrameSpare

' TenthFrameSpare handles the throwing of another ball and the
'   calculation of the 10th frame if there is a spare in the 10th.

Pause                            ' pause for extra ball
DrawPins 10                      ' set up a fresh rack of 10 pins
firstBowl% = GetRandBowl%(10)    ' get a random number for extra ball
ShowBowl firstBowl%              ' show the ball rolling down the alley
DrawPins 10 - firstBowl%         ' take away the pins that fell
 
IF (firstBowl% = 10) THEN        ' if the extra ball is a 10 then
    LOCATE FRAMEROW%, 61         '   locate the extra slot
    PRINT "X"                    '   and display an "X" for strike
ELSE
    LOCATE FRAMEROW%, 60         ' if the extra ball is not 10 then
    PRINT firstBowl%             '   print its value in extra slot
END IF

ProcessSpare firstBowl%, 11      ' calculate the final value of the
                                 '   10th frame
END SUB

SUB TenthFrameStrike

' TenthFrameStrike handles the throwing of two extra balls and any
'   unresolved frames if a strike is bowled in the 10th frame.

Pause                           ' pause for first ball
DrawPins 10                     ' set up a fresh rack of 10 pins
firstBowl% = GetRandBowl%(10)   ' get random number for first ball
ShowBowl firstBowl%             ' show the ball rolling down the alley
DrawPins 10 - firstBowl%        ' take away the pins that fell

IF (firstBowl% = 10) THEN       ' if the first ball is a 10 then
    LOCATE FRAMEROW%, 59            ' locate the second slot
    PRINT "X"                       ' and display an "X" for strike

    IF (strike% = 2) THEN           ' if working on a double,
        total% = total% + 10 + 10 + firstBowl%  ' get last frame total
        LOCATE SCOREROW%, 3 + (8 * 6)           ' locate last frame
        PRINT USING score$; total%              ' record frame score
    END IF

    Pause                           ' pause for second ball
    DrawPins 10                     ' set up a fresh rack of 10 pins
    secondBowl% = GetRandBowl%(10)  ' get a random number for ball
    ShowBowl secondBowl%            ' show the ball rolling down the alley
    DrawPins 10 - secondBowl%       ' take away the pins that fell
    IF (secondBowl% = 10) THEN      ' if this ball is also a 10,
        LOCATE FRAMEROW%, 61            ' locate the extra slot
        PRINT "X"                       ' and display an "X" for strike
    ELSE
        LOCATE FRAMEROW%, 60        ' if the ball is not 10 then
        PRINT secondBowl%               ' print its value in extra slot
    END IF
ELSE                            ' if the first ball is not a 10,
    IF (strike% = 2) THEN           ' process the remaining double (if any)
        total% = total% + 10 + 10 + firstBowl%  ' get last frame total
        LOCATE SCOREROW%, 3 + (8 * 6)           ' locate last frame
        PRINT USING score$; total%              ' display frame score
    END IF

    secondBowl% = GetRandBowl%(10 - firstBowl%) ' get second ball
    ShowBowl secondBowl% + firstBowl%           ' show ball
    DrawPins 10 - (secondBowl% + firstBowl%)    ' draw remaining pins
    LOCATE FRAMEROW%, 58
    PRINT firstBowl%                            ' display first ball
    IF (secondBowl% + firstBowl% = 10) THEN     ' if 10 down then
        LOCATE FRAMEROW%, 61                        ' print a "/" for
        PRINT "/"                                   ' spare
    ELSE
        LOCATE FRAMEROW%, 60                    ' if less than 10,
        PRINT secondBowl%                           ' print the number
    END IF
END IF

total% = total% + 10 + firstBowl% + secondBowl%  ' calculate and
LOCATE SCOREROW%, 57                             '   display final frame
PRINT USING score$; total%

END SUB

