DEFINT A-Z
OPTION BASE 1

DECLARE FUNCTION CheckPath% (i, IBound, IStep, j, JBound, JStep, Opponent)
DECLARE FUNCTION ValidMove% (Opponent)
DECLARE SUB ComputerMove ()
DECLARE SUB DisplayHelp ()
DECLARE SUB DisplayMsg (a$)
DECLARE SUB DrawCursor (row, col)
DECLARE SUB DrawGamePiece (row, col, PieceColor)
DECLARE SUB GameOver ()
DECLARE SUB InitGame ()
DECLARE SUB TakeBlocks (row, col, player)
DECLARE SUB UpdateScore ()
DECLARE SUB UserMove ()
DECLARE SUB DrawGameBoard ()

CONST TRUE = -1
CONST FALSE = 0
CONST QUIT = 113
CONST UP = 72
CONST DOWN = 80
CONST LEFT = 75
CONST RIGHT = 77
CONST BBLOCK = 1
CONST EBLOCK = 8
CONST ENTER = 13
CONST ULEFT = 71
CONST URIGHT = 73
CONST DLEFT = 79
CONST DRIGHT = 81
CONST PASS = 112
CONST DIFF = 100
CONST START = 115
CONST HELP = 104
CONST FMOVE = 99
CONST SPACE = 32

TYPE GameGrid
    player AS INTEGER
    nTake  AS INTEGER
    cx     AS INTEGER
    cy     AS INTEGER
END TYPE

TYPE GameStatus
    curRow   AS INTEGER
    curCol   AS INTEGER
    stat     AS INTEGER
    rScore   AS INTEGER
    bScore   AS INTEGER
    mDisplay AS INTEGER
    dLevel   AS STRING * 6
    GColor   AS INTEGER
END TYPE

DIM SHARED GS AS GameStatus, smode AS INTEGER
DIM SHARED GG(8, 8) AS GameGrid, GBoard AS INTEGER
DIM SHARED COMP AS INTEGER, HUMAN AS INTEGER, BG AS INTEGER
DIM SHARED GP(8, 8, 8) AS INTEGER, GW(8, 8) AS INTEGER

ON ERROR GOTO BadMode

DO
  READ smode
  vmode = TRUE
  SCREEN smode
LOOP UNTIL vmode = TRUE

IF smode = 0 THEN
  CLS
  LOCATE 10, 15: PRINT "No graphics screen mode available; cannot run REVERSI.BAS"
ELSE
  GS.stat = START
  GS.dLevel = "Novice"
  WHILE GS.stat <> QUIT
    IF GS.stat = START THEN
      InitGame
      DrawGameBoard
    END IF
    IF GS.stat <> COMP THEN
      IF ValidMove(COMP) THEN
        UserMove
      ELSEIF ValidMove(HUMAN) THEN
        DO
          DisplayMsg "You have no valid moves.  Select pass."
          DO
            a$ = INKEY$
          LOOP UNTIL a$ <> ""
        LOOP UNTIL ASC(RIGHT$(a$, 1)) = PASS
        LINE (0, 420)-(640, 447), 3, BF
        GS.mDisplay = FALSE
        GS.stat = COMP
        ComputerMove
      ELSE
        GameOver
      END IF
    ELSE
      IF ValidMove(HUMAN) THEN
        ComputerMove
      ELSEIF ValidMove(COMP) THEN
        DisplayMsg "Computer has no valid moves.  Your Turn."
        GS.stat = HUMAN
        UserMove
      ELSE
        GameOver
      END IF
    END IF
  WEND
  DisplayMsg "Game Over"
END IF

DATA 9, 10, 2, 3, 0

BadMode:
  vmode = FALSE
RESUME NEXT

END

FUNCTION CheckPath (i, IBound, IStep, j, JBound, JStep, Opponent)

  done = FALSE
  WHILE (i <> IBound OR j <> JBound) AND NOT done
    IF GG(i, j).player = GBoard THEN
      count = 0
      done = TRUE
    ELSEIF GG(i, j).player = Opponent THEN
      count = count + 1
      i = i + IStep
      j = j + JStep
      IF (i < 1 OR i > 8) OR (j < 1 OR j > 8) THEN
        count = 0
        done = TRUE
      END IF
    ELSE
      done = TRUE
    END IF
  WEND
  CheckPath = count
    
END FUNCTION

SUB ComputerMove
  BestMove = -99
  FOR row = 1 TO 8
    FOR col = 1 TO 8
      IF GG(row, col).nTake > 0 THEN
        IF GS.dLevel = "Novice" THEN
          value = GG(row, col).nTake + GW(row, col)
        ELSE
          value = GG(row, col).nTake + GW(row, col)
          SELECT CASE row
            CASE 1
              IF col < 5 THEN value = value + ABS(10 * GG(1, 1).player = COMP)
              IF col > 4 THEN value = value + ABS(10 * GG(1, 8).player = COMP)
            CASE 2
              IF GG(1, col).player <> COMP THEN value = value + 5 * (GG(1, col).player = HUMAN)
              IF col > 1 AND GG(1, col - 1).player <> COMP THEN value = value + 5 * (GG(1, col - 1).player = HUMAN)
              IF col < 8 AND GG(1, col + 1).player <> COMP THEN value = value + 5 * (GG(1, col + 1).player = HUMAN)
            CASE 7
              IF GG(8, col).player <> COMP THEN value = value + 5 * (GG(8, col).player = HUMAN)
              IF col > 1 AND GG(8, col - 1).player <> COMP THEN value = value + 5 * (GG(8, col - 1).player = HUMAN)
              IF col < 8 AND GG(8, col + 1).player <> COMP THEN value = value + 5 * (GG(8, col + 1).player = HUMAN)
            CASE 8
              IF col < 5 THEN value = value + ABS(10 * GG(8, 1).player = COMP)
              IF col > 4 THEN value = value + ABS(10 * GG(8, 8).player = COMP)
          END SELECT
          SELECT CASE col
            CASE 1
              IF row < 5 THEN value = value + ABS(10 * GG(1, 1).player = COMP)
              IF row > 4 THEN value = value + ABS(10 * GG(8, 1).player = COMP)
            CASE 2
              IF GG(row, 1).player <> COMP THEN value = value + 5 * (GG(row, 1).player = HUMAN)
              IF row > 1 AND GG(row - 1, 1).player <> COMP THEN value = value + 5 * (GG(row - 1, 1).player = HUMAN)
              IF row < 8 AND GG(row + 1, 1).player <> COMP THEN value = value + 5 * (GG(row + 1, 1).player = HUMAN)
            CASE 7
              IF GG(row, 8).player <> COMP THEN value = value + 5 * (GG(row, 8).player = HUMAN)
              IF row > 1 AND GG(row - 1, 8).player <> COMP THEN value = value + 5 * (GG(row - 1, 8).player = HUMAN)
              IF row < 8 AND GG(row + 1, 8).player <> COMP THEN value = value + 5 * (GG(row + 1, 8).player = HUMAN)
            CASE 8
              IF row < 5 THEN value = value + ABS(10 * GG(1, 8).player = COMP)
              IF row > 4 THEN value = value + ABS(10 * GG(8, 8).player = COMP)
          END SELECT
        END IF
        IF value > BestMove THEN
          BestMove = value
          bestrow = row
          bestcol = col
        END IF
      END IF
    NEXT col
  NEXT row

  TakeBlocks bestrow, bestcol, COMP
  GS.stat = HUMAN

END SUB

SUB DisplayHelp

  DIM a$(1 TO 18)

  a$(1) = "The object of Reversi is to finish the game with more of your red"
  a$(2) = "circles on the board than the computer has of blue (Monochrome"
  a$(3) = "monitors will show red as white and blue as black)."
  a$(4) = ""
  a$(5) = "1) You and the computer play by the same rules."
  a$(6) = "2) To make a legal move, at least one of the computer's circles"
  a$(7) = "   must lie in a horizontal, vertical, or diagonal line between"
  a$(8) = "   one of your existing circles and the square where you want to"
  a$(9) = "   move.  Use the arrow keys to position the cursor on the square"
  a$(10) = "   and hit Enter or the Space Bar."
  a$(11) = "3) You can choose Pass from the game controls menu on your first"
  a$(12) = "   move to force the computer to play first."
  a$(13) = "4) After your first move, you cannot pass if you can make a legal"
  a$(14) = "   move."
  a$(15) = "5) If you cannot make a legal move, you must choose Pass"
  a$(16) = "6) When neither you nor the computer can make a legal move, the"
  a$(17) = "   game is over."
  a$(18) = "7) The one with the most circles wins."

  LINE (0, 0)-(640, 480), BG, BF
  LINE (39, 15)-(590, 450), 0, B
  IF GBoard = 85 THEN
    PAINT (200, 200), CHR$(85), 0
  ELSE
    PAINT (200, 200), GBoard, 0
  END IF
  LINE (590, 25)-(600, 460), 0, BF
  LINE (50, 450)-(600, 460), 0, BF

  LOCATE 2, 35: PRINT "REVERSI HELP"
  FOR i = 1 TO 18
    LOCATE 3 + i, 7
    PRINT a$(i)
  NEXT i
  LOCATE 23, 25: PRINT "- Press any key to continue -"
  SLEEP: a$ = INKEY$
  DrawGameBoard
  DrawCursor GS.curRow, GS.curCol

END SUB

SUB DisplayMsg (a$)

  slen = LEN(a$)
  LX = (640 - 8 * (slen + 8)) / 2
  LINE (LX - 1, 420)-(640 - LX, 447), 0, B
  IF GBoard = 85 THEN
    PAINT (LX + 10, 430), CHR$(85), 0
  ELSE
    PAINT (LX + 10, 430), GBoard, 0
  END IF
  LOCATE 23, (80 - slen) / 2
  PRINT a$;
  GS.mDisplay = TRUE

END SUB

SUB DrawCursor (row, col)
  IF GG(row, col).nTake > 0 THEN
    CIRCLE (GG(row, col).cx, GG(row, col).cy), 15, HUMAN
    CIRCLE (GG(row, col).cx, GG(row, col).cy), 14, HUMAN
  ELSE
    lc = 0
    IF GG(row, col).player = 0 THEN lc = 7
    LINE (GG(row, col).cx, GG(row, col).cy - 15)-(GG(row, col).cx, GG(row, col).cy + 15), lc
    LINE (GG(row, col).cx - 1, GG(row, col).cy - 15)-(GG(row, col).cx - 1, GG(row, col).cy + 15), lc
    LINE (GG(row, col).cx + 15, GG(row, col).cy)-(GG(row, col).cx - 15, GG(row, col).cy), lc
  END IF
END SUB

SUB DrawGameBoard

  LINE (0, 0)-(640, 480), BG, BF
  LINE (239, 15)-(400, 40), 0, B
  LINE (39, 260)-(231, 390), 0, B
  LINE (39, 70)-(231, 220), 0, B
  LINE (269, 70)-(591, 390), 0, B

  IF GBoard = 85 THEN                  'If b&w
    PAINT (300, 25), CHR$(85), 0
    PAINT (150, 350), CHR$(85), 0
    PAINT (150, 124), CHR$(85), 0
    PAINT (450, 225), CHR$(85), 0
  ELSE
    PAINT (300, 25), GBoard, 0
    PAINT (150, 350), GBoard, 0
    PAINT (150, 124), GBoard, 0
    PAINT (450, 225), GBoard, 0
  END IF
  LINE (400, 25)-(410, 50), 0, BF
  LINE (250, 40)-(410, 50), 0, BF
  LINE (231, 80)-(240, 230), 0, BF
  LINE (50, 220)-(240, 230), 0, BF
  LINE (590, 80)-(600, 400), 0, BF
  LINE (280, 390)-(600, 400), 0, BF
  LINE (231, 270)-(240, 400), 0, BF
  LINE (50, 390)-(240, 400), 0, BF

  FOR i = 0 TO 8
    LINE (270, 70 + i * 40)-(590, 70 + i * 40), 0
    LINE (270 + i * 40, 70)-(270 + i * 40, 390), 0
    LINE (269 + i * 40, 70)-(269 + i * 40, 390), 0
  NEXT i
  
  LOCATE 2, 35: PRINT "R E V E R S I"

  LOCATE 5, 11: PRINT "Game Controls"
  LOCATE 7, 7: PRINT "S = Start New Game"
  LOCATE 8, 7: PRINT "P = Pass Turn"
  LOCATE 9, 7: PRINT "D = Set Difficulty"
  LOCATE 10, 7: PRINT "H = Display Help"
  LOCATE 11, 7: PRINT "Q = Quit"
  LOCATE 15, 12: PRINT "Game Status"
  LOCATE 17, 7: PRINT "Your Score:      "; GS.rScore; ""
  LOCATE 18, 7: PRINT "Computer Score:  "; GS.bScore
  LOCATE 20, 7: PRINT "Difficulty:   "; GS.dLevel

  FOR row = 1 TO 8
    FOR col = 1 TO 8
      IF GG(row, col).player <> GBoard THEN
        DrawGamePiece row, col, GG(row, col).player
      END IF
    NEXT col
  NEXT row

END SUB

SUB DrawGamePiece (row, col, GpColor)

  IF GBoard = 85 THEN
    LINE (232 + col * 40, 33 + row * 40)-(267 + col * 40, 67 + row * 40), 7, BF
    IF GpColor <> GBoard THEN
      CIRCLE (GG(row, col).cx, GG(row, col).cy), 15, 0
      PAINT (GG(row, col).cx, GG(row, col).cy), GpColor, 0
    END IF
    PAINT (235 + col * 40, 35 + row * 40), CHR$(85), 0
  ELSE
    CIRCLE (GG(row, col).cx, GG(row, col).cy), 15, GpColor
    CIRCLE (GG(row, col).cx, GG(row, col).cy), 14, GpColor
    PAINT (GG(row, col).cx, GG(row, col).cy), GpColor, GpColor
  END IF

END SUB

SUB GameOver
  Scorediff = GS.rScore - GS.bScore
  IF Scorediff = 0 THEN
    DisplayMsg "Tie Game"
  ELSEIF Scorediff < 0 THEN
    DisplayMsg "You lost by"
    PRINT ABS(Scorediff)
  ELSE
    DisplayMsg "You won by"
    PRINT Scorediff
  END IF
  DO
    GS.stat = ASC(RIGHT$(INKEY$, 1))
  LOOP UNTIL GS.stat = QUIT OR GS.stat = START
  LINE (0, 420)-(640, 447), BG, BF
END SUB

SUB InitGame
  SELECT CASE smode
    CASE 9:
      HUMAN = 4
      COMP = 1
      BG = 3
      GBoard = 8
    CASE ELSE:
      HUMAN = 7
      COMP = 0
      BG = 7
      IF smode = 10 THEN
        GBoard = 1
      ELSE
        GBoard = 85
      END IF
  END SELECT

  WINDOW SCREEN (640, 480)-(0, 0)
  GS.curCol = 5
  GS.curRow = 3
  GS.stat = FMOVE
  GS.bScore = 2
  GS.rScore = 2
  GS.mDisplay = FALSE

  FOR row = 1 TO 8
    FOR col = 1 TO 8
      GG(row, col).player = GBoard
      GG(row, col).nTake = 0
      GG(row, col).cx = 270 + (col - .5) * 40
      GG(row, col).cy = 70 + (row - .5) * 40
      GW(row, col) = 2
    NEXT col
  NEXT row
  GW(1, 1) = 99
  GW(1, 8) = 99
  GW(8, 1) = 99
  GW(8, 8) = 99
  FOR i = 3 TO 6
    FOR j = 1 TO 8 STEP 7
      GW(i, j) = 5
      GW(j, i) = 5
    NEXT j
  NEXT i
  GG(4, 4).player = HUMAN
  GG(5, 4).player = COMP
  GG(4, 5).player = COMP
  GG(5, 5).player = HUMAN
END SUB

SUB TakeBlocks (row, col, player)

  GG(row, col).player = player
  DrawGamePiece row, col, player

  FOR i = 1 TO GP(row, col, 1)
    GG(row, col - i).player = player
    DrawGamePiece row, col - i, player
  NEXT i
  FOR i = 1 TO GP(row, col, 2)
    GG(row, col + i).player = player
    DrawGamePiece row, col + i, player
  NEXT i
  FOR i = 1 TO GP(row, col, 3)
    GG(row - i, col).player = player
    DrawGamePiece row - i, col, player
  NEXT i
  FOR i = 1 TO GP(row, col, 4)
    GG(row + i, col).player = player
    DrawGamePiece row + i, col, player
  NEXT i
  FOR i = 1 TO GP(row, col, 5)
    GG(row - i, col - i).player = player
    DrawGamePiece row - i, col - i, player
  NEXT i
  FOR i = 1 TO GP(row, col, 6)
    GG(row + i, col + i).player = player
    DrawGamePiece row + i, col + i, player
  NEXT i
  FOR i = 1 TO GP(row, col, 7)
    GG(row - i, col + i).player = player
    DrawGamePiece row - i, col + i, player
  NEXT i
  FOR i = 1 TO GP(row, col, 8)
    GG(row + i, col - i).player = player
    DrawGamePiece row + i, col - i, player
  NEXT i

  IF player = HUMAN THEN
    GS.rScore = GS.rScore + GG(row, col).nTake + 1
    GS.bScore = GS.bScore - GG(row, col).nTake
  ELSE
    GS.bScore = GS.bScore + GG(row, col).nTake + 1
    GS.rScore = GS.rScore - GG(row, col).nTake
  END IF

  LOCATE 17, 7: PRINT "Your Score:      "; GS.rScore
  LOCATE 18, 7: PRINT "Computer Score:  "; GS.bScore

END SUB

SUB UserMove

  DrawCursor GS.curRow, GS.curCol
  DO
    DO
      a$ = INKEY$
    LOOP UNTIL a$ <> ""
    move = ASC(RIGHT$(a$, 1))
    IF GS.mDisplay THEN
      LINE (0, 420)-(640, 447), BG, BF
      GS.mDisplay = FALSE
    END IF
    SELECT CASE move
      CASE 71 TO 81:
        DrawGamePiece GS.curRow, GS.curCol, GG(GS.curRow, GS.curCol).player
        IF move < 74 THEN
          IF GS.curRow = BBLOCK THEN
            GS.curRow = EBLOCK
          ELSE
            GS.curRow = GS.curRow - 1
          END IF
        ELSEIF move > 78 THEN
          IF GS.curRow = EBLOCK THEN
            GS.curRow = BBLOCK
          ELSE
            GS.curRow = GS.curRow + 1
          END IF
        END IF
        IF move = 71 OR move = 75 OR move = 79 THEN
          IF GS.curCol = BBLOCK THEN
            GS.curCol = EBLOCK
          ELSE
            GS.curCol = GS.curCol - 1
          END IF
        ELSEIF move = 73 OR move = 77 OR move = 81 THEN
          IF GS.curCol = EBLOCK THEN
            GS.curCol = BBLOCK
          ELSE
            GS.curCol = GS.curCol + 1
          END IF
        END IF
        DrawCursor GS.curRow, GS.curCol
      CASE START:
        GS.stat = START
      CASE PASS:
        IF GS.stat = FMOVE THEN
          DisplayMsg "You passed.  Computer will make first move."
          GS.stat = COMP
        ELSE
          DisplayMsg "You can only pass on your first turn."
        END IF
      CASE HELP:
        DisplayHelp
      CASE DIFF:
        IF GS.dLevel = "Novice" THEN
          GS.dLevel = "Expert"
        ELSE
          GS.dLevel = "Novice"
        END IF
        LOCATE 20, 7
        PRINT "Difficulty:   "; GS.dLevel;
      CASE ENTER, SPACE:
        IF GG(GS.curRow, GS.curCol).nTake > 0 THEN
          TakeBlocks GS.curRow, GS.curCol, HUMAN
          GS.stat = COMP
        ELSE
          DisplayMsg "Invalid move.  Move to a space where the cursor is a circle."
        END IF
      CASE QUIT:
        GS.stat = QUIT
    END SELECT
  LOOP UNTIL GS.stat <> HUMAN AND GS.stat <> FMOVE

END SUB

FUNCTION ValidMove (Opponent)

  ValidMove = FALSE
  ERASE GP
  FOR row = 1 TO 8
    FOR col = 1 TO 8
      GG(row, col).nTake = 0

      IF GG(row, col).player = GBoard THEN
        IF col > 2 THEN
          GP(row, col, 1) = CheckPath(row, row, 0, col - 1, 0, -1, Opponent)
          GG(row, col).nTake = GG(row, col).nTake + GP(row, col, 1)
        END IF
        IF col < 7 THEN
          GP(row, col, 2) = CheckPath(row, row, 0, col + 1, 9, 1, Opponent)
          GG(row, col).nTake = GG(row, col).nTake + GP(row, col, 2)
        END IF
        IF row > 2 THEN
          GP(row, col, 3) = CheckPath(row - 1, 0, -1, col, col, 0, Opponent)
          GG(row, col).nTake = GG(row, col).nTake + GP(row, col, 3)
        END IF
        IF row < 7 THEN
          GP(row, col, 4) = CheckPath(row + 1, 9, 1, col, col, 0, Opponent)
          GG(row, col).nTake = GG(row, col).nTake + GP(row, col, 4)
        END IF
        IF col > 2 AND row > 2 THEN
          GP(row, col, 5) = CheckPath(row - 1, 0, -1, col - 1, 0, -1, Opponent)
          GG(row, col).nTake = GG(row, col).nTake + GP(row, col, 5)
        END IF
        IF col < 7 AND row < 7 THEN
          GP(row, col, 6) = CheckPath(row + 1, 9, 1, col + 1, 9, 1, Opponent)
          GG(row, col).nTake = GG(row, col).nTake + GP(row, col, 6)
        END IF
        IF col < 7 AND row > 2 THEN
          GP(row, col, 7) = CheckPath(row - 1, 0, -1, col + 1, 9, 1, Opponent)
          GG(row, col).nTake = GG(row, col).nTake + GP(row, col, 7)
        END IF
        IF col > 2 AND row < 7 THEN
          GP(row, col, 8) = CheckPath(row + 1, 9, 1, col - 1, 0, -1, Opponent)
          GG(row, col).nTake = GG(row, col).nTake + GP(row, col, 8)
        END IF
        IF GG(row, col).nTake > 0 THEN ValidMove = TRUE
      END IF
    NEXT col
  NEXT row

END FUNCTION

