'===========================================================================
' Subject: Text Based Mine Sweeper            Date: 10-19-03 (  :  )       
'  Author: Michael Webster                    Code: QB, PDS                
'  Origin: mfwebster@pdq.net                Packet: GAMES.ABC
'===========================================================================
' MINES.BAS
'
' This is a QuickBASIC text-mode ripoff of the
' Windows Minesweeper game. I believe I coded this
' soon after the Windows Minesweeper game appeared,
' whenever that was. In case you're wondering why
' the program does not use color and checks for
' video mode 7, it was coded on a system with an
' MDA and a green phosphor display.
'
' Note that this game requires a mouse - it cannot
' be played with the keyboard alone.

DECLARE SUB GetBackground ()
DECLARE SUB ShowHelp ()
DECLARE SUB PutBackground ()
DECLARE SUB MouseLocate (row%, col%)
DECLARE FUNCTION AllMarked% ()
DECLARE SUB Box (row1%, col1%, row2%, col2%, border%, fill%, title$)
DECLARE SUB ClearAround (row%, col%)
DECLARE SUB DrawBoard ()
DECLARE FUNCTION FieldClick% (fieldRow%, fieldCol%, mouseLeft%)
DECLARE SUB InitMineField ()
DECLARE FUNCTION InputBox$ (row%, col%, textLength%)
DECLARE SUB MainLoop ()
DECLARE SUB MouseDriver (m0%, m1%, m2%, m3%)
DECLARE SUB MouseHide ()
DECLARE FUNCTION MouseInit% ()
DECLARE SUB MousePoll (mouseRow%, mouseCol%, mouseLeft%, mouseRight%)
DECLARE SUB MouseShow ()
DECLARE FUNCTION NewButtonClick% ()
DECLARE FUNCTION OnFieldSquare% (mouseRow%, mouseCol%, fieldRow%, fieldCol%)
DECLARE SUB RecordBestTime ()
DECLARE FUNCTION ShiftFlags% ()
DECLARE SUB UpdateElapTime (resetFlag%)
DECLARE SUB UpdateCount ()
DECLARE FUNCTION VideoMode% ()
DECLARE SUB UncoverAll ()
DEFINT A-Z

CONST FALSE = 0, true = NOT FALSE

' Define data type and declare global variables for
' the InterruptX routine.
TYPE RegTypeX
    ax      AS INTEGER
    bx      AS INTEGER
    cx      AS INTEGER
    dx      AS INTEGER
    bp      AS INTEGER
    si      AS INTEGER
    di      AS INTEGER
    Flags   AS INTEGER
    ds      AS INTEGER
    es      AS INTEGER
END TYPE
DIM SHARED inRegs AS RegTypeX
DIM SHARED outRegs AS RegTypeX
DECLARE SUB InterruptX (intnum AS INTEGER, inRegs AS RegTypeX, outRegs AS RegTypeX)

' Had to increase the stack size for expert mode.
CLEAR , , 8196

' Declare and initialize a global string to store the
' background for the help screen.
DIM SHARED background$
background$ = SPACE$(4000)

' Declare a global variables to store the initial
' number of mines and the number of unmarked mines.
DIM SHARED mineCount, unmarkedMines

' Declare global variables to store the difficulty
' level and minefield size.
DIM SHARED expertMode, fieldCols

' Check for expert option and set operating parameters.
IF INSTR(COMMAND$, "E") THEN
    expertMode = true
    fieldCols = 16
    mineCount = 20
    unmarkedMines = 20
ELSE
    fieldCols = 8
    mineCount = 10
    unmarkedMines = 10
END IF

' Define a data type for storing the labels, tags, and screen coordinates
' of the minefield squares.  The tag element is used to indicate that the
' square has been processed.
TYPE MineFieldType
    label       AS STRING * 1
    tag         AS STRING * 1
    row         AS INTEGER
    col         AS INTEGER
END TYPE

' Declare a 2 dimensional array of the above type.
' Make the array larger than the actual minefield
' so the initialization code can be cleaner and
' simpler.
DIM SHARED minefield(10, fieldCols + 2) AS MineFieldType

' Declare a global variable to store the elapsed
' time and the state of the timer.
DIM SHARED elapsedTime, timerOn

' Initialize the minefield array character elements.
InitMineField

' Declare a global variable to store the and the
' error code from the most recent file operation.
DIM SHARED errorCode

CLS

' Attempt to initialize the mouse. If the
' initialization fails, then display an error
' message and quit.
IF MouseInit = 0 THEN
    LOCATE 10, 25
    PRINT "MINE requires a mouse.";
    END
END IF

' Draw the game board.
DrawBoard

' Switch the mouse cursor on.
MouseShow

' Start the main loop.
MainLoop

MouseHide
CLS
END

ErrorHandler:

    ' Handles any error that results from
    ' the attempt to open MINE.INI.
    errorCode = ERR
    RESUME NEXT

FUNCTION AllMarked

    ' Returns true if all the mines are marked.

    FOR row = 1 TO 8
        FOR col = 1 TO fieldCols
            IF minefield(row, col).label = "*" AND minefield(row, col).tag <> "X" THEN EXIT FUNCTION
        NEXT
    NEXT
    AllMarked = true

END FUNCTION

SUB Box (row1, col1, row2, col2, border, fill, title$) STATIC
   
    ' Displays a text mode window with a single or
    ' double line border, an optional fill, and an
    ' auto-centered title.

    IF border = 1 THEN
        upperLeft = 218
        upperRight = 191
        lowerLeft = 192
        lowerRight = 217
        horz = 196
        vert = 179
    ELSE
        upperLeft = 201
        upperRight = 187
        lowerLeft = 200
        lowerRight = 188
        horz = 205
        vert = 186
    END IF

    DIM row$(1 TO 25)

    IF title$ <> "" THEN
        leftLength = (col2 - col1 - LEN(title$) - 3) \ 2
        rightLength = (col2 - col1 - LEN(title$) - leftLength - 3)
        row$(row1) = CHR$(upperLeft) + STRING$(leftLength, horz) + " " + title$ + " " + STRING$(rightLength, horz) + CHR$(upperRight)
    ELSE
        row$(row1) = CHR$(upperLeft) + STRING$(col2 - col1 - 1, horz) + CHR$(upperRight)
    END IF

    IF fill THEN
        FOR row = row1 + 1 TO row2 - 1
            row$(row) = CHR$(vert) + SPACE$(col2 - col1 - 1) + CHR$(vert)
        NEXT
    END IF

    row$(row2) = CHR$(lowerLeft) + STRING$(col2 - col1 - 1, horz) + CHR$(lowerRight)

    LOCATE row1, col1, 0
    IF fill THEN
        FOR row = row1 TO row2
            LOCATE row, col1
            PRINT row$(row);
        NEXT
    ELSE
        PRINT row$(row1);
        FOR row = row1 + 1 TO row2 - 1
            LOCATE row, col1
            PRINT CHR$(vert);
            LOCATE row, col2
            PRINT CHR$(vert);
        NEXT
        LOCATE row2, col1
        PRINT row$(row2);
    END IF

END SUB

SUB ClearAround (row, col)

    ' Displays the label for all minefield squares
    ' adjacent to the square at <row, col>. If the
    ' corresponding label for any adjacent square
    ' is a "0", then this procedure clears around
    ' that square by performing a recursive call.
    ' The tag element is used to ensure that each
    ' square is processed only once.  Otherwise,
    ' the resulting unlimited recursion would
    ' quickly consume the stack.

    FOR r = row - 1 TO row + 1
        FOR c = col - 1 TO col + 1
            IF r > 0 AND r <= 8 AND c > 0 AND c <= fieldCols THEN
                IF minefield(r, c).tag = CHR$(0) THEN
                    LOCATE minefield(r, c).row, minefield(r, c).col + 1
                    MouseHide
                    PRINT minefield(r, c).label
                    MouseShow
                    minefield(r, c).tag = minefield(r, c).label
                    IF minefield(r, c).label = "0" THEN ClearAround r, c
                END IF
            END IF
        NEXT
    NEXT

END SUB

SUB DrawBoard
   
    ' Draws the game board, initializes the mine
    ' count and timer boxes, and loads the minefield
    ' square screen coordinates.

    IF expertMode THEN
        Box 2, 6, 23, 74, 2, FALSE, "Mines"
        Box 3, 10, 5, 18, 1, FALSE, ""
        UpdateCount
        Box 3, 36, 5, 44, 1, FALSE, ""
        LOCATE 4, 39
        PRINT "New"
        Box 3, 62, 5, 70, 1, FALSE, ""
        LOCATE 6, 8
        PRINT "ÚÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄ¿";
        LOCATE 7, 8
        PRINT "³   ³   ³   ³   ³   ³   ³   ³   ³   ³   ³   ³   ³   ³   ³   ³   ³";
        FOR row = 8 TO 20 STEP 2
            LOCATE row, 8
            PRINT "ÃÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄ´";
            LOCATE row + 1, 8
            PRINT "³   ³   ³   ³   ³   ³   ³   ³   ³   ³   ³   ³   ³   ³   ³   ³   ³";
        NEXT
        LOCATE 22, 8
        PRINT "ÀÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÙ";
    ELSE
        Box 2, 22, 23, 58, 2, FALSE, "Mines"
        Box 3, 24, 5, 32, 1, FALSE, ""
        UpdateCount
        Box 3, 36, 5, 44, 1, FALSE, ""
        LOCATE 4, 39
        PRINT "New"
        Box 3, 48, 5, 56, 1, FALSE, ""
        LOCATE 6, 24
        PRINT "ÚÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄ¿";
        LOCATE 7, 24
        PRINT "³   ³   ³   ³   ³   ³   ³   ³   ³";
        FOR row = 8 TO 20 STEP 2
            LOCATE row, 24
            PRINT "ÃÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄ´";
            LOCATE row + 1, 24
            PRINT "³   ³   ³   ³   ³   ³   ³   ³   ³";
        NEXT
        LOCATE 22, 24
        PRINT "ÀÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÙ";
    END IF

    timerOn = true
    UpdateElapTime true
    timerOn = FALSE

    FOR row = 1 TO 8
        FOR col = 1 TO fieldCols
            minefield(row, col).row = 7 + (row - 1) * 2
            IF fieldCols = 8 THEN
                minefield(row, col).col = 25 + (col - 1) * 4
            ELSE
                minefield(row, col).col = 9 + (col - 1) * 4
            END IF
        NEXT
    NEXT

END SUB

FUNCTION FieldClick (fieldRow, fieldCol, mouseLeft)

    ' Monitors and controls the minefield squares.
    ' If the left or right mouse button is down and
    ' the mouse cursor is on a minefield square, then
    ' the mouse cursor is positioned at the center of
    ' the square and the square is highlighted. If
    ' the mouse cursor is moved off the minefield
    ' square, or the mouse button is released, then
    ' the highlighted square is un-highlighted.
    ' Returns true if the mouse button is released
    ' with the mouse cursor on a minefield square.
    ' Note that this procedure does not return while
    ' a minefield square is highlighted. The arguments
    ' are used to return the minefield grid coordinates
    ' and the identity of the mouse button.

    DO
        MousePoll mouseRow, mouseCol, mouseLeft, mouseRight
        IF NOT mouseLeft AND NOT mouseRight AND NOT highlight THEN EXIT FUNCTION
        IF highlight THEN
            IF mouseLeft OR mouseRight THEN
                IF OnFieldSquare(mouseRow, mouseCol, fieldRow, fieldCol) THEN
                    IF fieldRow <> highlightRow OR fieldCol <> highlightCol THEN
                        LOCATE minefield(highlightRow, highlightCol).row, minefield(highlightRow, highlightCol).col
                        MouseHide
                        PRINT " "; minefield(highlightRow, highlightCol).tag; " ";
                        MouseShow
                        LOCATE minefield(fieldRow, fieldCol).row, minefield(fieldRow, fieldCol).col
                        MouseHide
                        PRINT " "; minefield(fieldRow, fieldCol).tag; " ";
                        MouseShow
                    END IF
                ELSE
                    LOCATE minefield(highlightRow, highlightCol).row, minefield(highlightRow, highlightCol).col
                    MouseHide
                    PRINT " "; minefield(highlightRow, highlightCol).tag; " ";
                    MouseShow
                    highlight = FALSE
                    EXIT FUNCTION
                END IF
            ELSE
                LOCATE minefield(highlightRow, highlightCol).row, minefield(highlightRow, highlightCol).col
                MouseHide
                PRINT " "; minefield(highlightRow, highlightCol).tag; " ";
                MouseShow
                FieldClick = true
                mouseLeft = prevMouseLeft
                EXIT FUNCTION
            END IF
        ELSE
            IF OnFieldSquare(mouseRow, mouseCol, fieldRow, fieldCol) THEN
                prevMouseLeft = mouseLeft
                COLOR 0, 7
                LOCATE minefield(fieldRow, fieldCol).row, minefield(fieldRow, fieldCol).col
                MouseHide
                PRINT " "; minefield(fieldRow, fieldCol).tag; " ";
                MouseShow
                COLOR 7, 0
                highlight = true
                highlightRow = fieldRow
                highlightCol = fieldCol
            ELSE
                EXIT FUNCTION
            END IF
        END IF
    LOOP

END FUNCTION

SUB GetBackground

    ' Crude (and slow) procedure to copy the screen
    ' contents to a global buffer. SCREEN 0 (80 X 25
    ' text) and display page 0 are assumed.

    IF VideoMode = 7 THEN
        DEF SEG = &HB000
    ELSE
        DEF SEG = &HB800
    END IF

    FOR i = 0 TO 3999
        MID$(background$, i + 1, 1) = CHR$(PEEK(i))
    NEXT

    DEF SEG

END SUB

SUB InitMineField

    ' Initializes the mineField array label elements.
    ' The array is larger than the actual field so
    ' the code in this procedure can be cleaner and
    ' simpler. Places mines in mineCount ramdomly
    ' selected elements and the number of adjacent
    ' mines in the elements without mines.

    RANDOMIZE TIMER
    FOR mine = 1 TO mineCount
        DO
            row = INT(RND * 8) + 1
            col = INT(RND * fieldCols) + 1
        LOOP UNTIL minefield(row, col).label <> "*"
        minefield(row, col).label = "*"
    NEXT

    FOR row = 1 TO 8
        FOR col = 1 TO fieldCols
            IF minefield(row, col).label <> "*" THEN
                count = 0
                FOR r = row - 1 TO row + 1
                    FOR c = col - 1 TO col + 1
                        IF minefield(r, c).label = "*" THEN count = count + 1
                    NEXT
                NEXT
                minefield(row, col).label = RIGHT$(STR$(count), 1)
            END IF
        NEXT
    NEXT

END SUB

FUNCTION InputBox$ (row, col, textLength)

    ' Reads and returns a single line of text from
    ' the keyboard. The input area is surrounded by
    ' a single line box. The coordinates <row> and
    ' <col> are for the upper left hand corner of
    ' the box. The box is 3 characters tall and
    ' (<textLength> + 4) characters wide. The maximum
    ' input line length is 32K (no check). If the
    ' input line length exceeds the maximum visible
    ' length (<textLength>), then the text will scroll
    ' horizontally. The editing features are similar
    ' to the INPUT statement. Returns the current text
    ' when the user presses Enter. Returns a null
    ' string when the user presses Escape. If the
    ' video mode is 7 then then a monochrome display
    ' is assumed. If the video mode is not 7 then CGA
    ' cursor emulation is assumed.

    IF VideoMode = 7 THEN
        insertStart = 12
        insertStop = 13
        typeOverStart = 0
        typeOverStop = 13
    ELSE
        insertStart = 7
        insertStop = 8
        typeOverStart = 0
        typeOverStop = 7
    END IF

    Box row, col, row + 2, col + textLength + 3, 1, true, ""

    firstChar = 1
    firstCol = col + 2
    lastCol = col + textLength + 2
    currentCol = firstCol
    tabStops = 8
    insertState = -1

    DO

        LOCATE row + 1, firstCol

        ' Print temporary string
        PRINT MID$(temp$, firstChar, textLength);

        ' Fill to end of box to overwrite any unused
        cursorCol = POS(0)
        IF cursorCol < lastCol THEN PRINT SPACE$(lastCol - cursorCol);

        ' Locate cursor, make visible, and set size
        IF insertState THEN
            LOCATE , currentCol, 1, insertStart, insertStop
        ELSE
            LOCATE , currentCol, 1, typeOverStart, typeOverStop
        END IF

        ' Wait for the user to press a key
        DO
            key$ = INKEY$
        LOOP UNTIL key$ <> ""

        ' Process the key
        SELECT CASE key$
            CASE CHR$(8)                    ' Backspace
                IF currentCol > firstCol OR firstChar > 1 THEN
                    leftPart$ = LEFT$(temp$, firstChar + currentCol - firstCol - 2)
                    rightPart$ = MID$(temp$, firstChar + currentCol - firstCol)
                    IF currentCol > firstCol THEN
                        currentCol = currentCol - 1
                    ELSEIF firstChar > 1 THEN
                        firstChar = firstChar - 1
                    END IF
                ELSE
                    leftPart$ = LEFT$(temp$, firstChar + currentCol - firstCol - 1)
                    rightPart$ = MID$(temp$, firstChar + currentCol - firstCol + 1)
                END IF
                temp$ = leftPart$ + rightPart$
            CASE CHR$(13)                   ' Enter
                EXIT DO
            CASE CHR$(27)                   ' Escape
                temp$ = ""
                EXIT DO
            CASE CHR$(0) + CHR$(71)         ' Home
                firstChar = 1
                currentCol = firstCol
            CASE CHR$(0) + CHR$(75)         ' Left
                IF currentCol > firstCol THEN
                    currentCol = currentCol - 1
                ELSEIF currentCol = firstCol AND firstChar > 1 THEN
                    firstChar = firstChar - 1
                END IF
            CASE CHR$(0) + CHR$(77)         ' Right
                IF currentCol < lastCol THEN
                    currentCol = currentCol + 1
                ELSEIF currentCol = lastCol THEN
                    firstChar = firstChar + 1
                END IF

            CASE CHR$(0) + CHR$(79)         ' End
                tempLength = LEN(temp$)
                IF tempLength > textLength THEN
                    firstChar = tempLength - textLength + 1
                ELSE
                    firstChar = 1
                END IF
                currentCol = firstCol + tempLength
                IF currentCol > lastCol THEN currentCol = lastCol

            CASE CHR$(0) + CHR$(82)         ' Ins
                ' Toggle insert state
                insertState = NOT insertState

            CASE CHR$(0) + CHR$(83)         ' Delete
                leftPart$ = LEFT$(temp$, firstChar + currentCol - firstCol - 1)
                rightPart$ = MID$(temp$, firstChar + currentCol - firstCol + 1)
                temp$ = leftPart$ + rightPart$

            CASE CHR$(32) TO CHR$(126)
                spaceCount = firstChar + currentCol - firstCol - LEN(temp$) - 1
                IF spaceCount > 0 THEN
                    temp$ = temp$ + SPACE$(spaceCount)
                END IF
                leftPart$ = LEFT$(temp$, firstChar + currentCol - firstCol - 1)
                IF insertState THEN
                    rightPart$ = MID$(temp$, firstChar + currentCol - firstCol)
                ELSE
                    rightPart$ = MID$(temp$, firstChar + currentCol - firstCol + 1)
                END IF
                temp$ = leftPart$ + key$ + rightPart$
                IF currentCol < lastCol THEN
                    currentCol = currentCol + 1
                ELSEIF currentCol = lastCol THEN
                    firstChar = firstChar + 1
                END IF
        END SELECT

        ' Make cursor invisible before looping.
        ' Partial fix for minor visual flaw in
        ' LOCATE statement.
        LOCATE , , 0
    LOOP

    ' Return string
    InputBox$ = temp$

    ' Make the cursor invisible before leaving.
    LOCATE , , 0

END FUNCTION

SUB MainLoop

    ' Processes user input, looping until the user
    ' presses the Escape key. If the user presses F1
    ' then a help screen is displayed. If the user
    ' clicks the new-game button, then the game is
    ' re-initialized. If the user clicks a minefield
    ' square with the left mouse button, then the
    ' label for that square is uncovered. If the
    ' label is a "*" (a mine), then the game is over
    ' (the timer stops but the user can continue to
    ' uncover labels). If any uncovered label is a
    ' "0", then the labels for the adjacent squares
    ' are uncovered. If the user clicks a blank square
    ' with the right mouse button, then the square is
    ' marked with an "X" and the mine count is
    ' decremented. If the user clicks a marked square
    ' with the left mouse button, then the label for
    ' that square is uncovered and the mine count is
    ' incremented. Each time a label is uncovered or
    ' a square is marked, the corresponding tag is set.
    ' The user wins the game when all the mines are
    ' marked and no mines have been uncovered. If the
    ' elapsed time for the game is lower than the
    ' previous best, then the user is prompted for
    ' a name.

    DO
        IF FieldClick(fieldRow, fieldCol, mouseLeft) THEN
            IF mouseLeft THEN
                IF NOT timerLock AND NOT timerOn THEN
                    timerOn = true
                    UpdateElapTime true
                END IF
                LOCATE minefield(fieldRow, fieldCol).row, minefield(fieldRow, fieldCol).col + 1
                MouseHide
                PRINT minefield(fieldRow, fieldCol).label;
                MouseShow
                IF minefield(fieldRow, fieldCol).tag = "X" THEN
                    minefield(fieldRow, fieldCol).tag = minefield(fieldRow, fieldCol).label
                    unmarkedMines = unmarkedMines + 1
                    UpdateCount
                END IF
                minefield(fieldRow, fieldCol).tag = minefield(fieldRow, fieldCol).label
                IF minefield(fieldRow, fieldCol).label = "0" THEN
                    ClearAround fieldRow, fieldCol
                ELSEIF minefield(fieldRow, fieldCol).label = "*" THEN
                    minefield(fieldRow, fieldCol).tag = "*"
                    timerOn = FALSE
                    timerLock = true
                    SOUND 120, 1
                    SOUND 80, 1
                    SOUND 40, 2
                END IF
            ELSE
                IF NOT timerLock AND NOT timerOn THEN
                    timerOn = true
                    UpdateElapTime true
                END IF
                IF minefield(fieldRow, fieldCol).tag = CHR$(0) AND unmarkedMines > 0 THEN
                    LOCATE minefield(fieldRow, fieldCol).row, minefield(fieldRow, fieldCol).col + 1
                    MouseHide
                    PRINT "X";
                    MouseShow
                    minefield(fieldRow, fieldCol).tag = "X"
                    unmarkedMines = unmarkedMines - 1
                    UpdateCount
                    IF AllMarked THEN
                        UncoverAll
                        timerOn = FALSE
                        RecordBestTime
                        UpdateElapTime true
                    END IF
                END IF
            END IF
        END IF

        IF NewButtonClick THEN
            timerLock = FALSE
            FOR row = 1 TO 8
                FOR col = 1 TO fieldCols
                    minefield(row, col).label = CHR$(0)
                    minefield(row, col).tag = CHR$(0)
                NEXT
            NEXT
            unmarkedMines = mineCount
            InitMineField
            DrawBoard
        END IF

        UpdateElapTime FALSE

        key$ = INKEY$

        IF key$ = CHR$(0) + CHR$(59) THEN
            MouseHide
            GetBackground
            ShowHelp
            DO
            LOOP UNTIL INKEY$ = CHR$(27)
            CLS
            PutBackground
            MouseShow
        END IF

    LOOP UNTIL key$ = CHR$(27)

END SUB

SUB MouseDriver (m0, m1, m2, m3) STATIC
  
    ' Provides a call interface for mouse driver
    ' functions. The first argument must specify
    ' the function, and the remaining arguments
    ' must specify any additional parameters
    ' required by that function. You must verify
    ' the presence of the mouse driver before
    ' calling this procedure.

    inRegs.ax = m0
    inRegs.bx = m1
    inRegs.cx = m2
    inRegs.dx = m3
    InterruptX &H33, inRegs, outRegs
    m0 = outRegs.ax
    m1 = outRegs.bx
    m2 = outRegs.cx
    m3 = outRegs.dx


END SUB

SUB MouseHide STATIC

    ' Decrements the mouse driver's internal cursor
    ' flag and hides the mouse cursor. The cursor
    ' flag is decremented for each call even if the
    ' mouse cursor is hidden.

    MouseDriver 2, 0, 0, 0
   
END SUB

FUNCTION MouseInit
    
    ' Checks for an installed mouse driver and
    ' attempts to reset the driver. Returns TRUE
    ' if successful, or FALSE if the mouse driver
    ' is not found or the reset failed. If the
    ' driver interrupt vector is 0000:0000 or the
    ' vector points to an IRET instruction, then
    ' the mouse driver is not installed. The mouse
    ' driver reset function (function 0) will return
    ' with m0 = 0 if the reset fails.

    DEF SEG = 0
    mouseSegment& = &H100& * PEEK(&HCF) + PEEK(&HCE)
    mouseOffset& = &H100& * PEEK(&HCD) + PEEK(&HCC)
    DEF SEG = mouseSegment&

    IF (mouseSegment& OR mouseOffset&) AND PEEK(mouseOfsetf&) <> &HCF THEN
        MouseDriver m0, m1, m2, m3
    END IF
    MouseInit = m0
    DEF SEG

END FUNCTION

SUB MouseLocate (row, col)

    ' Positions the mouse cursor at <row>,<col>.
    ' SCREEN 0 (80 X 25 text) is is assumed.
    ' The cursor postion is specified in character
    ' coordinates. The mouse driver expects the
    ' cursor position to be in virtual-screen
    ' coordinates (640 X 200).

    m2 = col * 8 - 1
    m3 = row * 8 - 1
    MouseDriver 4, m1, m2, m3

END SUB

SUB MousePoll (mouseRow, mouseCol, mouseLeft, mouseRight) STATIC
  
    ' Returns the current mouse cursor position and
    ' button status. SCREEN 0 (80 X 25 text) is assumed.
    ' The cursor postion is returned in character
    ' coordinates. The mouse driver returns the cursor
    ' position in virtual-screen coordinates (640 X 200).
   
    MouseDriver 3, m1, m2, m3
    mouseRow = m3 \ 8 + 1
    mouseCol = m2 \ 8 + 1
    IF m1 AND 1 THEN mouseLeft = true ELSE mouseLeft = FALSE
    IF m1 AND 2 THEN mouseRight = true ELSE mouseRight = FALSE

END SUB

SUB MouseShow STATIC

    ' Increments the mouse driver's internal cursor flag.
    ' The mouse cursor will be displayed if the cursor
    ' flag is 0 after being incremented. The cursor flag
    ' will never be > 0.

    MouseDriver 1, 0, 0, 0

END SUB

FUNCTION NewButtonClick STATIC

    ' Monitors and controls the new-game button.
    ' If the left mouse button is down and the
    ' mouse cursor is on the new-game button, then
    ' the new-game button is highlighted. If the
    ' mouse cursor is moved off the new-game button,
    ' or if the mouse button is released, then the
    ' new-game button is un-highlighted. Returns
    ' true if the mouse button is released with the
    ' mouse cursor on the new-game button. The
    ' return value must be set to false for each call
    ' because all local variables retain there values
    ' between calls.

    NewButtonClick = FALSE

    MousePoll mouseRow, mouseCol, mouseLeft, mouseRight

    IF mouseRow = 4 AND mouseCol > 36 AND mouseCol < 42 THEN
        IF highlight THEN
            IF NOT mouseLeft THEN
                LOCATE 4, 38
                MouseHide
                PRINT " New ";
                MouseShow
                highlight = FALSE
                NewButtonClick = true
                EXIT FUNCTION
            END IF
        ELSE
            IF mouseLeft THEN
                COLOR 0, 7
                LOCATE 4, 38
                MouseHide
                PRINT " New ";
                MouseShow
                COLOR 7, 0
                highlight = true
            END IF
        END IF
    ELSE
        IF highlight THEN
            LOCATE 4, 38
            MouseHide
            PRINT " New ";
            MouseShow
            highlight = FALSE
        END IF
    END IF

END FUNCTION

FUNCTION OnFieldSquare (mouseRow, mouseCol, fieldRow, fieldCol)

    ' Returns true if the mouse cursor is on a minefield
    ' square. The arguments <fieldRow> and <fieldCol> are
    ' used to return the position of the mouse cursor in
    ' minefield grid coordinates.

    FOR fieldRow = 1 TO 8
        FOR fieldCol = 1 TO fieldCols
            IF mouseRow = minefield(fieldRow, fieldCol).row AND mouseCol >= minefield(fieldRow, fieldCol).col AND mouseCol < minefield(fieldRow, fieldCol).col + 3 THEN
                OnFieldSquare = true
                EXIT FUNCTION
            END IF
        NEXT
    NEXT

END FUNCTION

SUB PutBackground

    ' Crude (and slow) procedure to restore the
    ' screen contents from the global buffer.
    ' SCREEN 0 (80 X 25 text) and display page 0
    ' are assumed.

    IF VideoMode = 7 THEN
        DEF SEG = &HB000
    ELSE
        DEF SEG = &HB800
    END IF

    FOR i = 0 TO 3999
        POKE i, ASC(MID$(background$, i + 1, 1))
    NEXT

    DEF SEG

END SUB

SUB RecordBestTime

    ' Compares the elapsed time to the previous
    ' best time for the current mode and, if lower,
    ' records the user's name along with the new
    ' best time. Reads and writes the file
    ' MINES.INI. Creates a new file with default
    ' values if errors in the current file.
    ' Re-initializes the game when finished.

    ON ERROR GOTO ErrorHandler
    OPEN "MINES.INI" FOR INPUT AS 1
    IF errorCode = 0 THEN
        LINE INPUT #1, line$
        IF line$ = "[Minesweeper]" THEN
            LINE INPUT #1, line$
            time1Pos = INSTR(line$, "Time1=")
            IF time1Pos THEN
                bestTime1 = VAL(MID$(line$, time1Pos + 6))
                IF bestTime1 < 1 THEN badFile = true
                LINE INPUT #1, line$
                time2Pos = INSTR(line$, "Time2=")
                IF time2Pos THEN
                    bestTime2 = VAL(MID$(line$, time2Pos + 6))
                    IF bestTime2 < 1 THEN badFile = true
                    LINE INPUT #1, line$
                    name1Pos = INSTR(line$, "Name1=")
                    IF name1Pos THEN
                        bestName1$ = MID$(line$, name1Pos + 6)
                        IF bestName1$ = "" THEN badFile = true
                        LINE INPUT #1, line$
                        name2Pos = INSTR(line$, "Name2=")
                        IF name2Pos THEN
                            bestName2$ = MID$(line$, name2Pos + 6)
                            IF bestName2$ = "" THEN badFile = true
                        ELSE
                            badFile = true
                        END IF
                    ELSE
                        badFile = true
                    END IF
                ELSE
                    badFile = true
                END IF
            ELSE
                badFile = true
            END IF
        ELSE
            badFile = true
        END IF
        CLOSE 1
    END IF

    IF badFile OR errorCode THEN
        bestTime1 = 999
        bestTime2 = 999
        bestName1$ = "Anonymous"
        bestName2$ = "Anonymous"
    END IF

    Box 6, 24, 16, 56, 1, true, ""
    IF expertMode THEN
        IF elapsedTime < bestTime2 THEN
            LOCATE 8, 28
            PRINT "You have the fastest time";
            LOCATE 9, 30
            PRINT "for the expert level.";
            LOCATE 11, 29
            PRINT "Please type your name:";
            newName$ = LEFT$(InputBox(12, 28, 21), 25)
            IF newName$ <> "" THEN
                bestName2$ = newName$
                bestTime2 = elapsedTime
            END IF
        END IF
    ELSE
        IF elapsedTime < bestTime1 THEN
            LOCATE 8, 28
            PRINT "You have the fastest time";
            LOCATE 9, 29
            PRINT "for the beginner level.";
            LOCATE 11, 29
            PRINT "Please type your name:";
            newName$ = LEFT$(InputBox(12, 28, 21), 25)
            IF newName$ <> "" THEN
                bestName1$ = newName$
                bestTime1 = elapsedTime
            END IF
        END IF
    END IF
    errorCode = 0
    OPEN "MINES.INI" FOR OUTPUT AS 1
    IF errorCode = 0 THEN
        PRINT #1, "[Minesweeper]"
        PRINT #1, "Time1="; MID$(STR$(bestTime1), 2)
        PRINT #1, "Time2="; MID$(STR$(bestTime2), 2)
        PRINT #1, "Name1="; bestName1$
        PRINT #1, "Name2="; bestName2$
        CLOSE 1
    END IF

    ON ERROR GOTO 0

    Box 6, 24, 16, 56, 1, true, "Fastest Minesweepers"
    LOCATE 8, 30
    PRINT "Beginner:"; bestTime1; "seconds";
    LOCATE 10, 40 - LEN(bestName1$) \ 2
    PRINT bestName1$;
    LOCATE 12, 31
    PRINT "Expert:"; bestTime2; "seconds";
    LOCATE 14, 40 - LEN(bestName2$) \ 2
    PRINT bestName2$;

    DO
        key$ = INKEY$
    LOOP UNTIL key$ <> "" OR NewButtonClick

    FOR row = 1 TO 8
        FOR col = 1 TO fieldCols
            minefield(row, col).label = CHR$(0)
            minefield(row, col).tag = CHR$(0)
        NEXT
    NEXT
    unmarkedMines = mineCount
    InitMineField
    DrawBoard

END SUB

FUNCTION ShiftFlags
   
    ' Returns the BIOS shift flags byte (0040:0017).
  
    DEF SEG = &H40
    ShiftFlags = PEEK(&H17)
    DEF SEG

END FUNCTION

SUB ShowHelp

    Box 1, 1, 25, 80, 1, true, "Mines Help"
    LOCATE 3, 3
    PRINT "Mines is a simple text mode rip-off of the Windows Minesweeper game."
    LOCATE 4, 3
    PRINT "The game board consist of a mine count box, a new-game button, an elapsed"
    LOCATE 5, 3
    PRINT "time box, and a minefield represented as a grid of squares."
    LOCATE 6, 3
    PRINT "The object of the game is to mark all the squares containing a mine as"
    LOCATE 7, 3
    PRINT "quickly as possible without uncovering any squares containing a mine."
    LOCATE 8, 3
    PRINT "  Use the right mouse button to mark a square. Use the left mouse button to"
    LOCATE 9, 3
    PRINT "uncover a square. If you uncover a square with a mine then the game is over."
    LOCATE 10, 3
    PRINT "If you uncover a square without a mine, then a number will appear. This"
    LOCATE 11, 3
    PRINT "number indicates the number of mines in the adjacent squares. If any"
    LOCATE 12, 3
    PRINT "uncovered square contains a 0, then the program will uncover the adjacent"
    LOCATE 13, 3
    PRINT "squares for you."
    LOCATE 14, 3
    PRINT "  The mine count box initially contains the number of mines in the minefield."
    LOCATE 15, 3
    PRINT "The value in the mine count box is decreased each time you mark a square,"
    LOCATE 16, 3
    PRINT "even if you mark it incorrectly. The value in the mine count box is"
    LOCATE 17, 3
    PRINT "increased each time you uncover a marked square."
    LOCATE 18, 3
    PRINT "  The default is an 8 X 8 minefield with 10 mines. The Expert command line"
    LOCATE 19, 3
    PRINT "option increases this to a 16 X 8 minefield with 20 mines."
    LOCATE 20, 3
    PRINT "  The previous fastest times along with the names of the associated players"
    LOCATE 21, 3
    PRINT "are saved in the file MINES.INI. Delete that file to reset the saved data"
    LOCATE 22, 3
    PRINT "to default."
    
END SUB

SUB UncoverAll

    ' Uncovers all minefield squares without mines.
    FOR row = 1 TO 8
        FOR col = 1 TO fieldCols
            IF minefield(row, col).label <> "*" THEN
                LOCATE minefield(row, col).row, minefield(row, col).col + 1
                MouseHide
                PRINT minefield(row, col).label;
                MouseShow
            END IF
        NEXT
    NEXT

END SUB

SUB UpdateCount

    ' Displays the current value of the global
    ' variable unmarkedMines.

    unmarkedMines$ = MID$(STR$(unmarkedMines), 2)
    IF expertMode THEN LOCATE 4, 13 ELSE LOCATE 4, 27
    PRINT STRING$(3 - LEN(unmarkedMines$), "0"); unmarkedMines$;

END SUB

SUB UpdateElapTime (resetFlag) STATIC

    ' Updates the elapsed time display if the global
    ' variable timerOn is true. Resets the elapsed
    ' time (to 0) if <resetFlag>. Correctly handles
    ' the midnight rollover.
    
    IF timerOn THEN
        tm& = TIMER
        IF resetFlag THEN startTime& = tm&
        et& = tm& - startTime&
        IF et& < 0 THEN et& = 86400 - startTime& + tm&
        IF et& < 999 THEN
            elapsedTime = et&
            IF resetFlag OR elapsedTime > prevElapsedTime THEN
                prevElapsedTime = elapsedTime
                elapsedTime$ = MID$(STR$(elapsedTime), 2)
                IF expertMode THEN LOCATE 4, 65 ELSE LOCATE 4, 51
                MouseHide
                PRINT STRING$(3 - LEN(elapsedTime$), "0"); elapsedTime$;
                MouseShow
            END IF
        END IF
    END IF

END SUB

FUNCTION VideoMode

    ' Returns the current video mode setting from
    ' the BIOS data area.

    DEF SEG = &H40
    VideoMode = PEEK(&H49)
    DEF SEG

END FUNCTION

