rem **************************************************
rem **************************************************
rem
rem    Generic Williams Display Driver Functions
rem       Version 2.0 (Black Knight Version)
rem
rem      Written By Chris Leathley (ala Black)
rem
rem   Can be freely used in other tables providing
rem               credit is given
rem
rem This script can be modified by the user but
rem any modified scripts are to remain with the
rem owner and NOT reposted as enhancements to the
rem the display driver
rem
rem Requires the following elements on the table
rem --------------------------------------------
rem
rem Timers called 'DisplayTimer'
rem           and 'DisplayEffectTimer'
rem Variables named 'BallsPerGame' which contains
rem                 the number of balls per game
rem           and 'BallsRemaining' which contains
rem               the number of balls remaining
rem               this game
rem
rem revision history
rem ----------------
rem
rem v2.0  Added effect enums
rem       Added in Scroll Out and Scroll in Effects
rem       Added Fast Blink Effect
rem       Added in Trail In Multiplier Effect
rem       Added in Blink Mask effects
rem       Limititations of effects combinations removed
rem       Added length checking to DisplayQueueScreen()
rem       Fixed a few progmatic errors
rem       DisplayInit() API changed
rem
rem v1.1  Added Format Score functions for use with
rem       'WilliamsPinball.ttf' font
rem       Added sound functionality to displays
rem
rem v1.0  First Released with Black Knight 2000
rem
rem **************************************************
rem **************************************************

Option Explicit                   ' Force explicit variable declaration.

' user defined constants
Const dcCHARSPERLINE    = 16      ' Number of characters per text line (Williams/Bally default is 16)

' define effect constants (enum's)
Const eNone             = 0       ' Instantly displayed
Const eScrollLeft       = 1       ' scroll on from the right
Const eScrollRight      = 2       ' scroll on from the left
Const eBlink            = 3       ' Blink (blinks for 'TimeOn') at user specified intervals (slow speed)
                                  ' blink speed (either line) in milliseconds (default 100ms) must be multiple of dcEFFECTSPEED
                                  ' it takes 2*n to do a full cycle (n on and n off). DisplayQueueScreen TimeOn should be multiple
                                  ' of 2*n to ensure a smooth effect transition
Const eScrollOut        = 4       ' scroll out from middle to the edges
Const eScrollIn         = 5       ' scroll in the edges to the middle
Const eScrollLeftOver   = 6       ' scroll on from the right (over current text)
Const eScrollRightOver  = 7       ' scroll on from the left (over current text)
Const eBlinkFast        = 8       ' Blink (blinks for 'TimeOn') at user specified intervals (fast speed)
Const eTrailIn          = 9       ' Trail in towards the middle (usefull for only 2 characters in the middle)
Const eBlinkMask        = 10      ' Blinks only non space text in text over current text line (slow speed)
Const eBlinkMaskFast    = 11      ' Blinks only non space text in text over current text line (slow speed)

Dim DispQueueSize                 ' size of display queue (64)
Dim DispQueueHead                 ' head of queue (current display)
Dim DispQueueTail                 ' tail of queue (where to put new ones)
Dim DisplayBlankLine
Dim DispCurrentTopLine
Dim DispCurrentBottomLine
Dim DispEffectCountT1
Dim DispEffectCountT1End
Dim DispEffectBlinkCycleT1
Dim DispEffectCountT2
Dim DispEffectCountT2End
Dim DispEffectCountT2Dly
Dim DispEffectSpeed
Dim DispEffectBlinkSlowRate
Dim DispEffectBlinkFastRate
Dim DispEffectBlinkCycleT2
Dim DispQueueText1(64)            ' storage elements of the queue (would much prefer a structure)
Dim DispQueueText2(64)
Dim DispQueueEffectOnT1(64)
Dim DispQueueEffectOnT2(64)
Dim DispQueueEffectOnT2Dly(64)
Dim DispQueueTimeOn(64)
Dim DispQueuebFlush(64)
Dim DispQueueSound(64)

rem initialise the display driver
rem
Sub DisplayInit(EffectSpeed, SlowBlinkRate, QuickBlinkRate)
  Dim i

  ' define the queue size
  DispQueueSize = 64
	' flush it
  DisplayFlushQueue()
  ' set the specified parameters
  DispEffectSpeed         = EffectSpeed
  DispEffectBlinkSlowRate = SlowBlinkRate
  DispEffectBlinkFastRate = QuickBlinkRate
  ' set up the black line semi constant
  DisplayBlankLine = ""
  For i = 1 to dcCHARSPERLINE
    DisplayBlankLine = DisplayBlankLine & " "
  Next
  ' blank out the 2 lines on the display
  DispCurrentTopLine    = DisplayBlankLine
  DispCurrentBottomLine = DisplayBlankLine
  TextBoxTop.Text    = DispCurrentTopLine
	TextBoxBottom.Text = DispCurrentBottomLine
End Sub

rem flush the display queue, dosn't change the display in anyway
rem
Sub DisplayFlushQueue()
	' stop the display timers
	DisplayTimer.Enabled = False
	DisplayEffectTimer.Enabled = False
	' no displays in the queue
	DispQueueHead = 0
	DispQueueTail = 0
	' and certainly no effects
	DispEffectCountT1 = 0
	DispEffectCountT1End = 0
  DispEffectBlinkCycleT1 = 0
	DispEffectCountT2 = 0
	DispEffectCountT2End = 0
	DispEffectCountT2Dly = 0
  DispEffectBlinkCycleT2 = 0
End Sub

rem displays the score screen immediatly
rem
Sub DisplayScoreNow()
	' flush any other display's queued up and call the display score function
	DisplayFlushQueue()
	DisplayScore()
End Sub

rem display the current score and ball number (writes directly to the screen)
rem
Sub DisplayScore()
	Dim Balls
	Dim TempTopStr
  Dim TempBottomStr

  ' the queue must be empty for the score screen to be displayed
  if (DispQueueHead = DispQueueTail) and (HurryUpBonus = 0) then
    TempTopStr = FormatScore(Score)&"       "
    DispCurrentTopLine = TempTopStr
    TextBoxTop.Text    = TempTopStr
    ' if in ransom mode then display time remaining else # balls remaining
    if (bRansomGoSickMode = True) then
      Balls = RansomRemainingMs
      Balls = int(Balls / 1000)
      ' "        TIME### "
      TempBottomStr = "        TIME"&FormatScore1K(Balls, False, False)&" "
    else
      Balls = BallsPerGame + 1
      Balls = Balls - BallsRemaining
      ' "         BALL # "
      TempBottomStr = "         BALL "&Chr(Balls+48)&" "
    end if
    DispCurrentBottomLine = TempBottomStr
    TextBoxBottom.Text    = TempBottomStr
  end if
End Sub

rem this function queues up the score display with the specified effect(s)..
rem this is so a scroll can scroll back onto to default score screen
rem
Sub DisplayScoreQueue(EffectOnT1, EffectOnT2)
	Dim Balls
	Dim TempTopStr
  Dim TempBottomStr

  TempTopStr = FormatScore(Score)&"       "
	' if in ransom mode then display time remaining else # balls remaining
	if (bRansomGoSickMode = True) then
		Balls = RansomRemainingMs
		Balls = int(Balls / 1000)
    ' "        TIME### "
    TempBottomStr = "        TIME"&FormatScore1K(Balls, False, False)&" "
	else
    Balls = BallsPerGame + 1
		Balls = Balls - BallsRemaining
    ' "         BALL # "
    TempBottomStr = "         BALL "&Chr(Balls+48)&" "
	end if
  DisplayQueueScreen TempTopStr, TempBottomStr, EffectOnT1, EffectOnT2, 0, 25, True, ""
End Sub

rem this function returns the number of outstanding displays in the queue
rem
Function DisplayGetQueueSize()
	DisplayGetQueueSize = DispQueueTail - DispQueueHead
End Function

rem This function will add in a diusplay screen into the queue and fire it off if it is
rem the first display in the queue
rem
Sub DisplayQueueScreen(Text1, Text2, EffectOnT1, EffectOnT2, DelayT2, TimeOn, bFlush, Sound)
	' must be room in the queue
	if (DispQueueTail < DispQueueSize) then
    '
    if (Len(Text1) <> dcCHARSPERLINE and Text1 <> "-") or (Len(Text2) <> dcCHARSPERLINE and Text2 <> "-") then
      MsgBox "DISPLAY PROBLEM" & vbNewLine & vbNewLine & "ONE OF THE DISPLAY LINES SPECIFIED HAS A INCORECT LENGTH" & vbNewLine & "(ALL LENGTHS SHOULD BE " & dcCHARSPERLINE & " CHARACTERS or '-')" & vbNewLine & vbNewLine & "Text 1 '" & Text1 & "' Len=" & Len(Text1) & vbNewLine & "Text 2 '" & Text2 & "' Len=" & Len(Text2) & vbNewLine, 0, "Black Display Driver Error"
    else
      ' ensure no change lines ("-") have no effect
      if (Text1 = "-") then EffectOnT1 = eNone
      if (Text2 = "-") then EffectOnT2 = eNone
      ' save the details in the queue
      DispQueueText1(DispQueueTail)         = Text1
      DispQueueText2(DispQueueTail)         = Text2
      DispQueueEffectOnT1(DispQueueTail)    = EffectOnT1
      DispQueueEffectOnT2(DispQueueTail)    = EffectOnT2
      DispQueueEffectOnT2Dly(DispQueueTail) = DelayT2
      DispQueueTimeOn(DispQueueTail)        = TimeOn
      DispQueuebFlush(DispQueueTail)        = bFlush
      DispQueueSound(DispQueueTail)         = Sound
      ' move the to next queue slot
      DispQueueTail = DispQueueTail + 1
      ' if this is the first thing is the queue, then start the ball rolling (so to speak ;-)
      if (DispQueueTail = 1) then
        DisplayHead()
      end if
    end if
	end if
End Sub

rem Display the screen at the head of the queue
rem
Sub DisplayHead()
	' calculate the length of the effect required for the screen

	' set the start values for the effects
	DispEffectCountT1 = 0
	DispEffectCountT2 = 0

  ' set timer interval
  DisplayEffectTimer.Interval = DispEffectSpeed
  ' set delay between top line effect and bottom line effect (delay is n * effect time interval)
  DispEffectCountT2Dly = DispQueueEffectOnT2Dly(DispQueueHead)

  Select Case (DispQueueEffectOnT1(DispQueueHead))
    Case eNone:             DispEffectCountT1End = 1                                  ' Instantly Display (no effect)
    Case eScrollLeft:       DispEffectCountT1End = Len(DispQueueText1(DispQueueHead)) ' effect loops
    Case eScrollRight:      DispEffectCountT1End = Len(DispQueueText1(DispQueueHead))
    Case eBlink:            DispEffectCountT1End = int(DispQueueTimeOn(DispQueueHead) / DispEffectSpeed)
                            DispEffectBlinkCycleT1 = 0
    Case eScrollOut:        DispEffectCountT1End = Len(DispQueueText1(DispQueueHead))/2
    Case eScrollIn:         DispEffectCountT1End = Len(DispQueueText1(DispQueueHead))/2
    Case eScrollLeftOver:   DispEffectCountT1End = Len(DispQueueText1(DispQueueHead))
    Case eScrollRightOver:  DispEffectCountT1End = Len(DispQueueText1(DispQueueHead))
    Case eBlinkFast:        DispEffectCountT1End = int(DispQueueTimeOn(DispQueueHead) / DispEffectSpeed)
                            DispEffectBlinkCycleT1 = 0
    Case eTrailIn:          DispEffectCountT1End = Len(DispQueueText1(DispQueueHead))/2
                            DispCurrentTopLine = DisplayBlankLine
    Case eBlinkMask:        DispEffectCountT1End = int(DispQueueTimeOn(DispQueueHead) / DispEffectSpeed)
                            DispEffectBlinkCycleT1 = 0
    Case eBlinkMaskFast:    DispEffectCountT1End = int(DispQueueTimeOn(DispQueueHead) / DispEffectSpeed)
                            DispEffectBlinkCycleT1 = 0

	End Select

  Select Case (DispQueueEffectOnT2(DispQueueHead))
    Case eNone:             DispEffectCountT2End = 1
    Case eScrollLeft:       DispEffectCountT2End = Len(DispQueueText2(DispQueueHead))
    Case eScrollRight:      DispEffectCountT2End = Len(DispQueueText2(DispQueueHead))
    Case eBlink:            DispEffectCountT2End = int(DispQueueTimeOn(DispQueueHead) / DispEffectSpeed)
                            DispEffectBlinkCycleT2 = 0
    Case eScrollOut:        DispEffectCountT2End = Len(DispQueueText2(DispQueueHead))/2
    Case eScrollIn:         DispEffectCountT2End = Len(DispQueueText2(DispQueueHead))/2
    Case eScrollLeftOver:   DispEffectCountT2End = Len(DispQueueText2(DispQueueHead))
    Case eScrollRightOver:  DispEffectCountT2End = Len(DispQueueText2(DispQueueHead))
    Case eBlinkFast:        DispEffectCountT2End = int(DispQueueTimeOn(DispQueueHead) / DispEffectSpeed)
                            DispEffectBlinkCycleT2 = 0
    Case eTrailIn:          DispEffectCountT2End = Len(DispQueueText2(DispQueueHead))/2
                            DispCurrentBottomLine = DisplayBlankLine
    Case eBlinkMask:        DispEffectCountT2End = int(DispQueueTimeOn(DispQueueHead) / DispEffectSpeed)
                            DispEffectBlinkCycleT2 = 0
    Case eBlinkMaskFast:    DispEffectCountT2End = int(DispQueueTimeOn(DispQueueHead) / DispEffectSpeed)
                            DispEffectBlinkCycleT2 = 0
	End Select

	' if there is a sound for this screen then play it
	if (DispQueueSound(DispQueueHead) <> "") then
		PlaySound(DispQueueSound(DispQueueHead))
	end if

	' start the effect timer
	DisplayEffectTimer.Enabled = True
End Sub

rem The Effect Timer Has Expired, process the effect routine
rem
Sub DisplayEffectTimer_Timer()
	' stop the timer
	DisplayEffectTimer.Enabled = False
	' process the effect
	DisplayProcessEffectOn()
End Sub

rem the 'TimeOn' time has expired for the current (head) display
rem process the OFF effect
rem
Sub DisplayTimer_Timer()
	Dim Head

	' stop the timer
	DisplayTimer.Enabled = False

	' save the current head pointer
	Head = DispQueueHead
	' move to the next display in the queue (if there is one)
	DispQueueHead = DispQueueHead + 1
	' if head equal tail than at the end of queue then
	' restart the from the begining of the queue
	if (DispQueueHead = DispQueueTail) then
		' does this display want to flush the queue?
		if (DispQueuebFlush(Head) = True) then
			' yep
			DisplayFlushQueue()
			' return to the default screen which is the score display
			DisplayScore()
			' leave the timer off
		else
			' start again from the begining
			DispQueueHead = 0
			' and display it
			DisplayHead()
		end if
	else
		' display the next screen
		DisplayHead()
	end if
End Sub

rem This Function actually draws the text to the display using the effect spefified
rem
rem effects:  eNone             = Instantly displayed
rem           eScrollLeft       = scroll on from the left
rem           eScrollRight      = scroll on from the right
rem           eBlink            = Blink (blinks for 'TimeOn') at slow speed
rem           eScrollOut        = scroll out from middle to edges
rem           eScrollIn         = scroll in from the edges to the middle
rem           eScrollLeftOver   = scroll over from the left
rem           eScrollRightOver  = scroll over from the right
rem           eBlinkFast        = Blink (blinks for 'TimeOn') at fast speed
rem           eTrailIn          = Trails in middle 2 characters
rem           eBlinkMask        = Blink only active text in line (over current line)
rem           eBlinkMaskFast    = Blink only active text in line (over current line)
rem
Sub DisplayProcessEffectOn()
  Dim i
  Dim BlinkEffect
	Dim TempTopStr
  Dim TempBottomStr
  Dim TempLeftStr
  Dim TempRightStr
  Dim MaskCharacter

  BlinkEffect = False

	' process the first line (T1)
  TempLeftStr = ""
  TempRightStr = ""

	' notch one up on the effect cycle count (Text Line 1) providing we havn't already finished
	if (DispEffectCountT1 <> DispEffectCountT1End) then
		DispEffectCountT1 = DispEffectCountT1 + 1

    ' manipulate the line according to the effect
    select case (DispQueueEffectOnT1(DispQueueHead))
      case eNone:
        TempTopStr = DispQueueText1(DispQueueHead)

      case eScrollLeft:
        TempTopStr = Right(DispCurrentTopLine, dcCHARSPERLINE-1)
        TempTopStr = TempTopStr & Mid(DispQueueText1(DispQueueHead), DispEffectCountT1 ,1)

      case eScrollRight:
        TempTopStr = Mid(DispQueueText1(DispQueueHead), (dcCHARSPERLINE+1)-DispEffectCountT1 ,1)
        TempTopStr = TempTopStr & Left(DispCurrentTopLine, dcCHARSPERLINE-1)

      case eBlink:
        BlinkEffect = True
        if ((DispEffectCountT1 MOD DispEffectBlinkSlowRate) = 0) then
          DispEffectBlinkCycleT1 = DispEffectBlinkCycleT1 xor 1
        end if
        if (DispEffectBlinkCycleT1 = 0) then
          TempTopStr = DispQueueText1(DispQueueHead)
        else
          TempTopStr = DisplayBlankLine
        end if

      case eScrollOut:
        TempLeftStr  = Mid(DispCurrentTopLine, 2, (dcCHARSPERLINE/2)-1)
        TempLeftStr  = TempLeftStr & Mid(DispQueueText1(DispQueueHead), DispEffectCountT1, 1)
        TempRightStr = Mid(DispQueueText1(DispQueueHead), (dcCHARSPERLINE+1)-DispEffectCountT1, 1)
        TempRightStr = TempRightStr & Mid(DispCurrentTopLine, (dcCHARSPERLINE/2)+1, (dcCHARSPERLINE/2)-1)
        TempTopStr   = TempLeftStr & TempRightStr

      case eScrollIn:
        TempLeftStr  = Mid(DispQueueText1(DispQueueHead), ((dcCHARSPERLINE/2)+1)-DispEffectCountT1, 1)
        TempLeftStr  = TempLeftStr & Left(DispCurrentTopLine, (dcCHARSPERLINE/2)-1)
        TempRightStr = Right(DispCurrentTopLine, (dcCHARSPERLINE/2)-1)
        TempRightStr = TempRightStr & Mid(DispQueueText1(DispQueueHead), (dcCHARSPERLINE/2)+DispEffectCountT1, 1)
        TempTopStr   = TempLeftStr & TempRightStr

      case eScrollLeftOver:
        TempTopStr = Left(DispCurrentTopLine, dcCHARSPERLINE-DispEffectCountT1)
        TempTopStr = TempTopStr & Left(DispQueueText1(DispQueueHead), DispEffectCountT1)

      case eScrollRightOver:
        TempTopStr = Right(DispQueueText1(DispQueueHead), DispEffectCountT1)
        TempTopStr = TempTopStr & Right(DispCurrentTopLine, dcCHARSPERLINE-DispEffectCountT1)

      case eBlinkFast:
        BlinkEffect = True
        if ((DispEffectCountT1 MOD DispEffectBlinkFastRate) = 0) then
          DispEffectBlinkCycleT1 = DispEffectBlinkCycleT1 xor 1
        end if
        if (DispEffectBlinkCycleT1 = 0) then
          TempTopStr = DispQueueText1(DispQueueHead)
        else
          TempTopStr = DisplayBlankLine
        end if

      case eTrailIn:
        TempLeftStr = left(DispCurrentTopLine, DispEffectCountT1-1)
        TempLeftStr = TempLeftStr & Mid(DispQueueText1(DispQueueHead), (dcCHARSPERLINE/2), 1)
        TempLeftStr = TempLeftStr & Mid(DispCurrentTopLine, DispEffectCountT1+1, (dcCHARSPERLINE/2)-DispEffectCountT1)
        TempRightStr = Mid(DispCurrentTopLine, (dcCHARSPERLINE/2)+1, (dcCHARSPERLINE/2)-DispEffectCountT1)
        TempRightStr = TempRightStr & Mid(DispQueueText1(DispQueueHead), (dcCHARSPERLINE/2)+1, 1)
        TempRightStr = TempRightStr & right(DispCurrentTopLine, DispEffectCountT1-1)
        TempTopStr = TempLeftStr & TempRightStr

      case eBlinkMask
        TempTopStr = ""
        BlinkEffect = True
        if ((DispEffectCountT1 MOD DispEffectBlinkSlowRate) = 0) then
          DispEffectBlinkCycleT1 = DispEffectBlinkCycleT1 xor 1
        end if
        for i = 1 to dcCHARSPERLINE
          MaskCharacter = Mid(DispQueueText1(DispQueueHead), i, 1)
          if (MaskCharacter <> " ") then
            if (DispEffectBlinkCycleT1 = 0) then
              TempTopStr = TempTopStr & MaskCharacter
            else
              TempTopStr = TempTopStr & " "
            end if
          else
            TempTopStr = TempTopStr & Mid(DispCurrentTopLine, i, 1)
          end if
        next

      case eBlinkMaskFast
        TempTopStr = ""
        BlinkEffect = True
        if ((DispEffectCountT1 MOD DispEffectBlinkFastRate) = 0) then
          DispEffectBlinkCycleT1 = DispEffectBlinkCycleT1 xor 1
        end if
        for i = 1 to dcCHARSPERLINE
          MaskCharacter = Mid(DispQueueText1(DispQueueHead), i, 1)
          if (MaskCharacter <> " ") then
            if (DispEffectBlinkCycleT1 = 0) then
              TempTopStr = TempTopStr & MaskCharacter
            else
              TempTopStr = TempTopStr & " "
            end if
          else
            TempTopStr = TempTopStr & Mid(DispCurrentTopLine, i, 1)
          end if
        next

    End Select

    ' if the text is "-" then leave this line alone
    if (DispQueueText1(DispQueueHead) <> "-") then
      if (Len(TempTopStr) <> dcCHARSPERLINE) and (Len(TempTopStr) <> 0) then
        MsgBox "DISPLAY PROBLEM" & vbNewLine & vbNewLine & "INTERNAL ERROR: STRING TOO LONG" & vbNewLine & "TempTopStr '" & TempTopStr & "' Len=" & Len(TempTopStr), 0, "Black Display Driver Error"
      end if
      DispCurrentTopLine = TempTopStr
      TextBoxTop.Text = TempTopStr
    end if
	end if

	' process the second line (T2)
  TempLeftStr = ""
  TempRightStr = ""

	if (DispEffectCountT2 <> DispEffectCountT2End) then
		' notch one up on the effect cycle count (Text Line 1)
		if (DispEffectCountT2Dly = 0) then
			DispEffectCountT2 = DispEffectCountT2 + 1

      ' manipulate the line according to the effect
      select case (DispQueueEffectOnT2(DispQueueHead))
        case eNone:
          TempBottomStr = DispQueueText2(DispQueueHead)

        case eScrollLeft:
          TempBottomStr = Right(DispCurrentBottomLine, dcCHARSPERLINE-1)
          TempBottomStr = TempBottomStr & Mid(DispQueueText2(DispQueueHead), DispEffectCountT2 ,1)

        case eScrollRight:
          TempBottomStr = Mid(DispQueueText2(DispQueueHead), (dcCHARSPERLINE+1)-DispEffectCountT2 ,1)
          TempBottomStr = TempBottomStr & Left(DispCurrentBottomLine, dcCHARSPERLINE-1)

        case eBlink:
          BlinkEffect = True
          if ((DispEffectCountT2 MOD DispEffectBlinkSlowRate) = 0) then
            DispEffectBlinkCycleT2 = DispEffectBlinkCycleT2 xor 1
          end if
          if (DispEffectBlinkCycleT2 = 0) then
            TempBottomStr = DispQueueText2(DispQueueHead)
          else
            TempBottomStr = DisplayBlankLine
          end if

        case eScrollOut:
          TempLeftStr  = Mid(DispCurrentBottomLine, 2, (dcCHARSPERLINE/2)-1)
          TempLeftStr  = TempLeftStr & Mid(DispQueueText2(DispQueueHead), DispEffectCountT2, 1)
          TempRightStr = Mid(DispQueueText2(DispQueueHead), (dcCHARSPERLINE+1)-DispEffectCountT2, 1)
          TempRightStr = TempRightStr & Mid(DispCurrentBottomLine, (dcCHARSPERLINE/2)+1, (dcCHARSPERLINE/2)-1)
          TempBottomStr = TempLeftStr & TempRightStr

        case eScrollIn:
          TempLeftStr  = Mid(DispQueueText2(DispQueueHead), ((dcCHARSPERLINE/2)+1)-DispEffectCountT2, 1)
          TempLeftStr  = TempLeftStr & Left(DispCurrentBottomLine, (dcCHARSPERLINE/2)-1)
          TempRightStr = Right(DispCurrentBottomLine, (dcCHARSPERLINE/2)-1)
          TempRightStr = TempRightStr & Mid(DispQueueText2(DispQueueHead), (dcCHARSPERLINE/2)+DispEffectCountT2, 1)
          TempBottomStr = TempLeftStr & TempRightStr

        case eScrollLeftOver:
          TempBottomStr = Left(DispCurrentBottomLine, dcCHARSPERLINE-DispEffectCountT2)
          TempBottomStr = TempBottomStr & Left(DispQueueText2(DispQueueHead), DispEffectCountT2)

        case eScrollRightOver:
          TempBottomStr = Right(DispQueueText2(DispQueueHead), DispEffectCountT2)
          TempBottomStr = TempBottomStr & Right(DispCurrentBottomLine, dcCHARSPERLINE-DispEffectCountT2)

        case eBlinkFast:
          BlinkEffect = True
          if ((DispEffectCountT2 MOD DispEffectBlinkFastRate) = 0) then
            DispEffectBlinkCycleT2 = DispEffectBlinkCycleT2 xor 1
          end if
          if (DispEffectBlinkCycleT2 = 0) then
            TempBottomStr = DispQueueText2(DispQueueHead)
          else
            TempBottomStr = DisplayBlankLine
          end if

        case eTrailIn:
          TempLeftStr = left(DispCurrentBottomLine, DispEffectCountT2-1)
          TempLeftStr = TempLeftStr & Mid(DispQueueText2(DispQueueHead), (dcCHARSPERLINE/2), 1)
          TempLeftStr = TempLeftStr & Mid(DispCurrentBottomLine, DispEffectCountT2+1, (dcCHARSPERLINE/2)-DispEffectCountT2)
          TempRightStr = Mid(DispCurrentBottomLine, (dcCHARSPERLINE/2)+1, (dcCHARSPERLINE/2)-DispEffectCountT2)
          TempRightStr = TempRightStr & Mid(DispQueueText2(DispQueueHead), (dcCHARSPERLINE/2)+1, 1)
          TempRightStr = TempRightStr & right(DispCurrentBottomLine, DispEffectCountT2-1)
          TempBottomStr = TempLeftStr & TempRightStr

        case eBlinkMask
          TempBottomStr = ""
          BlinkEffect = True
          if ((DispEffectCountT2 MOD DispEffectBlinkSlowRate) = 0) then
            DispEffectBlinkCycleT2 = DispEffectBlinkCycleT2 xor 1
          end if
          for i = 1 to dcCHARSPERLINE
            MaskCharacter = Mid(DispQueueText2(DispQueueHead), i, 1)
            if (MaskCharacter <> " ") then
              if (DispEffectBlinkCycleT2 = 0) then
                TempBottomStr = TempBottomStr & MaskCharacter
              else
                TempBottomStr = TempBottomStr & " "
              end if
            else
              TempBottomStr = TempBottomStr & Mid(DispCurrentBottomLine, i, 1)
            end if
          next

        case eBlinkMaskFast
          TempBottomStr = ""
          BlinkEffect = True
          if ((DispEffectCountT2 MOD DispEffectBlinkFastRate) = 0) then
            DispEffectBlinkCycleT2 = DispEffectBlinkCycleT2 xor 1
          end if
          for i = 1 to dcCHARSPERLINE
            MaskCharacter = Mid(DispQueueText2(DispQueueHead), i, 1)
            if (MaskCharacter <> " ") then
              if (DispEffectBlinkCycleT2 = 0) then
                TempBottomStr = TempBottomStr & MaskCharacter
              else
                TempBottomStr = TempBottomStr & " "
              end if
            else
              TempBottomStr = TempBottomStr & Mid(DispCurrentBottomLine, i, 1)
            end if
          next

      End Select

      ' if the text is "-" then leave this line alone
      if (DispQueueText2(DispQueueHead) <> "-") then
        if (Len(TempBottomStr) <> dcCHARSPERLINE) and (Len(TempBottomStr) <> 0) then
          MsgBox "DISPLAY PROBLEM" & vbNewLine & vbNewLine & "INTERNAL ERROR: STRING TOO LONG" & vbNewLine & "TempBottomStr '" & TempBottomStr & "' Len=" & Len(TempBottomStr), 0, "Black Display Driver Error"
        end if
        DispCurrentBottomLine = TempBottomStr
        TextBoxBottom.Text = TempBottomStr
      end if
		else
			DispEffectCountT2Dly = DispEffectCountT2Dly - 1
		end if
	end if

	' have we run to the end of the effect cycle?
	if (DispEffectCountT1 = DispEffectCountT1End) and (DispEffectCountT2 = DispEffectCountT2End) then
		' leave the effect timer stopped

		' if 'TimeOn' = 0 then this screen never expires, flush the queue
		if (DispQueueTimeOn(DispQueueHead) = 0) then
			DisplayFlushQueue()
		else
      ' start the display timer for 'TimeOn' (the exception to this is 'Blink' effects as they
			' uses TimeOn for the Blink Length)
      if (BlinkEffect = True) then
        DisplayTimer.Interval  = 10 ' display expires basically immediatly
      else
        DisplayTimer.Interval  = DispQueueTimeOn(DispQueueHead)
      end if

			' and start the timer
			DisplayTimer.Enabled  = True
		end if
	else
		' else restart the effect timer
		DisplayEffectTimer.Enabled = True
	end if
End Sub

rem This function converts the specified number into a fixed length string (9 characters) using
rem the following formatting rules ###,###,### (or XXX,YYY,ZZZ).  This allows numbers under
rem 1 Billion to be formatted correctly for display on the score board
rem
Function FormatScore(ByVal Num)
	Dim Temp
	Dim NumString
	Dim bZero

	NumString = ""						' start off with an empty string
	bZero = False						' and space padding

	' handle XXX (001,yyy,zzz - 999,yyy,zzz)
	Temp = int(Num / 1000000)			' if digits in the 1M to 1Bil-1
	if (Temp > 0) then					' then convert this to ###,
		NumString = NumString & FormatScore1K(Temp,bZero,True)
		Temp = Temp * 1000000			' remove the 1M portion from the original number
		Num = Num - Temp
		bZero = True					' any numbers converted from now on have to be zero filled
	else
		NumString = NumString & "   "	' ###
	end if

	' handle YYY (xxx,001,zzz - xxx,999,zzz)
	Temp = int(Num / 1000)
	if (Temp > 0) then
		NumString = NumString & FormatScore1K(Temp,bZero,True)
		Temp = Temp * 1000
		Num = Num - Temp
		bZero = True
	else
		if (bZero = True) then
			NumString = NumString & "00À"	' or '###,'
		else
			NumString = NumString & "   "
		end if
	end if

	' convert the last thousand set (ZZZ or (xxx,yyy,001 - xxx,yyy,999))
	NumString = NumString & FormatScore1K(Num, bZero, False)
	' return the string to the calling routine
	FormatScore = NumString
End Function

rem This function converts the specified number into a fixed length string (6 characters) using
rem the following formatting rules ###,###.  This allows the numbers under 1 Million
rem to be formatted correctly for display on the score board
rem
Function FormatScore1M(ByVal Num)
	Dim Temp
	Dim NumString
	Dim bZero

	NumString = ""						' start off with an empty string
	bZero = False						' and space padding

	' handle XXX (001,yyy - 999,yyy)
	Temp = int(Num / 1000)				' if digits in the 1K to 1M-1
	if (Temp > 0) then					' then convert this to ###,
		NumString = NumString & FormatScore1K(Temp,bZero,True)
		Temp = Temp * 1000				' remove the 1K portion from the original number
		Num = Num - Temp
		bZero = True					' any numbers converted from now on have to be zero filled
	else
		NumString = NumString & "   "	' ###
	end if

	' convert the last thousand set (xxx,001 - xxx,999))
	NumString = NumString & FormatScore1K(Num, bZero, False)
	' return the string to the calling routine
	FormatScore1M = NumString
End Function

rem this function converts a number between 0 and 999 into a 3 byte string which
rem is right justified, using either a space or 0 as the passing character
rem
rem This routine is totally dedicated to my WilliamsPinball font which supports
rem the embedded , in the letters and numbers
rem
Function FormatScore1K(Num, bZero, bComma)
	Dim NewNum
	Dim	LastDigit

	' get the unit
	LastDigit = Num Mod 10
	' remove the unit from the number (just leaves 0 to 99 now)
	NewNum = int(Num / 10)

	if (bZero = True) then					' do we pad with 0 or space
		if (NewNum >= 10) then				' padding with 0, convert 10 - 99
			FormatScore1K = NewNum
		else
			FormatScore1K = "0" & NewNum	' convert 0 - 9
		end if
	else
		if (NewNum >= 10) then
			FormatScore1K = NewNum
		else
			if (NewNum <> 0) then
				FormatScore1K = " " & NewNum
			else
				FormatScore1K = "  "
			end if
		end if
	end if
	' if we are doing commas, then convert the last digit
	if (bComma = True) then
		FormatScore1K = FormatScore1K & Chr(LastDigit + 192)
	else
		FormatScore1K = FormatScore1K & LastDigit
	end if
End Function

rem This function returns a string which is either True or False depending of the state of Bool
rem
Function FormatBool(ByVal Bool)
	if (Bool = True) then
		FormatBool = "True"
	else
		FormatBool = "False"
	end if
End Function

