' D. de Haas
' Amsterdam, Netherlands
' 29 October 2002
' ddhadam@yahoo.com

Option Explicit On 
Option Strict On

Imports System
Imports System.Drawing
Imports DdH.Graphics

Public Class TetrisGameArea

    Private mX, mY, mWidth, mHeight, mBlockSize, mLinesCleared, mScore As Integer
    Private mLevel As Integer = 1
    Private mPause As Boolean

    Private Field As Graphics.PlayField
    Private MainShape As Graphics.Shape
    Private SpareShape1 As Graphics.Shape
    Private SpareShape2 As Graphics.Shape
    Private FallTimer As New Timers.Timer()
    Private IsGameOver As Boolean

    '==== Events

    Public Event Redraw(ByVal region As Drawing.Region)
    Public Event GameOver()

    '==== Constructors

    Public Sub New(ByVal x As Integer, ByVal y As Integer, ByVal blockSize As Integer, ByVal widthBlocks As Integer, ByVal heightBlocks As Integer)
        ' Create a new Tetris GameArea on postition (x,y) and (blocksize) in pixels
        ' and width (widthBlocks, heightBlocks) in blocks

        mX = x
        mY = y
        mBlockSize = blockSize
        mWidth = widthBlocks
        mHeight = heightBlocks

        ' Event handler for Timer
        AddHandler FallTimer.Elapsed, AddressOf Timer_Tick

        FallTimer.Enabled = True
        FallTimer.Stop()

        Me.Clear()
    End Sub

    '==== Private Methods/Functions

    Private Sub Timer_Tick(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs)
        ' Every timer tick move the shape down

        ' Exit if game is in Pause
        If mPause Then Exit Sub

        Me.Move(Graphics.Shape.ShapeMove.Down)
    End Sub

    Private Sub Field_UpdateLinesCleared(ByVal count As Integer)
        ' Update LinesCleared counter when a line is completed

        mLinesCleared += count

        ' Increase level, by counting cleared lines per level
        Dim m As Integer = 5 + (mLevel * 5)
        Dim n As Integer

        n = mLinesCleared - ((mLinesCleared \ m) * m)
        If (n Mod m) = 0 Then mLevel += 1

        ' After level 10, it's Game Over...
        If mLevel > 10 Then
            ' Men...You're the best :-)

            IsGameOver = True
            RaiseEvent GameOver()
        Else
            ' Increase score, more lines means a higher score

            mScore += (mWidth * count * count)
        End If
    End Sub

    Private Function ValidateMove(ByRef shape As Graphics.Shape, ByVal direction As Graphics.Shape.ShapeMove) As Boolean
        ' Validate if a shape can move in the specified direction
        Dim ts As Graphics.Shape

        ts = shape.Clone
        ts.Move(direction)

        Return Field.Validate(ts)
    End Function

    Private Function ValidateRotate(ByRef shape As Graphics.Shape, ByVal direction As Graphics.Shape.ShapeRotate) As Boolean
        ' Validate if a shape can rotate in the specified direction
        Dim ts As Graphics.Shape

        ts = shape.Clone
        ts.Rotate(direction)

        Return Field.Validate(ts)
    End Function

    Private Sub GetNewShape()
        ' Get a new randomize Shape 

        Dim r As New Random(Date.Now.Millisecond)
        Dim sh As Graphics.Shape.ShapeType

        ' Spare1
        If (SpareShape2 Is Nothing) Then
            ' First time here
            ' Set new Spare1

            Select Case r.Next(1, 8)
                Case 1
                    sh = Shape.ShapeType.Blok
                Case 2
                    sh = Shape.ShapeType.LeftL
                Case 3
                    sh = Shape.ShapeType.LeftS
                Case 4
                    sh = Shape.ShapeType.Line
                Case 5
                    sh = Shape.ShapeType.RightL
                Case 6
                    sh = Shape.ShapeType.RightS
                Case 7
                    sh = Shape.ShapeType.T
            End Select

            SpareShape1 = New Graphics.Shape(sh, mBlockSize)
        Else
            ' Change Spare2 to Spare1
            SpareShape1 = SpareShape2.Clone
        End If

        SpareShape1.SetPos(mX + ((mWidth + 2) * mBlockSize), mY)

        ' Spare2
        Select Case r.Next(1, 8)
            Case 1
                sh = Shape.ShapeType.Blok
            Case 2
                sh = Shape.ShapeType.LeftL
            Case 3
                sh = Shape.ShapeType.LeftS
            Case 4
                sh = Shape.ShapeType.Line
            Case 5
                sh = Shape.ShapeType.RightL
            Case 6
                sh = Shape.ShapeType.RightS
            Case 7
                sh = Shape.ShapeType.T
        End Select

        SpareShape2 = New Graphics.Shape(sh, mBlockSize)
        SpareShape2.SetPos(mX + ((mWidth + 2) * mBlockSize), mY + (5 * mBlockSize))
    End Sub

    '==== Public Methodes

    Public Sub Start()
        ' The Game starts here

        ' Set Shape from Spare1
        MainShape = SpareShape1.Clone

        ' Center Shape
        Dim x As Integer = (mWidth - MainShape.Width) \ 2
        If x + MainShape.Width > mWidth Then
            x = x - (x + MainShape.Width - mWidth)
        End If
        MainShape.SetPos(mX + (x * mBlockSize), mY)

        ' Validate before placing on the playfield
        If Not Field.Validate(MainShape) Then
            ' Field must be full, so Game Over

            IsGameOver = True
            RaiseEvent GameOver()

            Exit Sub
        End If

        ' Update Spares
        GetNewShape()

        ' Send that GameArea, can be redraw
        RaiseEvent Redraw(New Region(New Rectangle(mX, mY, (mWidth * mBlockSize), (mHeight * mBlockSize))))

        ' Send that Spare Blocks Area, can be redraw
        RaiseEvent Redraw(New Region(New Rectangle(mX + (mWidth * mBlockSize), mY, ((mWidth + 5) * mBlockSize), (10 * mBlockSize))))

        ' Fall speed
        FallTimer.Interval = 1050 - (mLevel * 100)
        FallTimer.Start()
    End Sub

    Public Sub Clear()
        ' Set a GameArea ready for a new Game

        Field = New Graphics.PlayField(mX, mY, mBlockSize, mWidth, mHeight)

        ' Event handler for PlayField
        AddHandler Field.LinesCleared, AddressOf Field_UpdateLinesCleared

        IsGameOver = False
        mLinesCleared = 0
        mScore = 0
        mPause = False

        ' Update Spares
        GetNewShape()
    End Sub

    Public Sub Draw(ByRef graph As System.Drawing.Graphics)
        ' Draws the Game Area and it's shapes

        If Not (Field Is Nothing) Then Field.Draw(graph)
        If Not (MainShape Is Nothing) Then MainShape.Draw(graph)
        If Not (SpareShape1 Is Nothing) Then SpareShape1.Draw(graph)
        If Not (SpareShape2 Is Nothing) Then SpareShape2.Draw(graph)
    End Sub

    Public Sub Move(ByVal direction As Graphics.Shape.ShapeMove)
        ' Move shape in the specified direction

        ' Exit when Game Over
        If IsGameOver Then Exit Sub

        ' Exit if game is in Pause
        If mPause Then Exit Sub

        ' Validate before moving
        If ValidateMove(MainShape, direction) Then
            MainShape.Move(direction)

            ' Send that GameArea, can be redraw
            RaiseEvent Redraw(New Region(New Rectangle(mX, mY, (mWidth * mBlockSize), (mHeight * mBlockSize))))
        Else
            ' Can't move

            If direction = Shape.ShapeMove.Down Then
                ' Movement as down, so get new shape

                FallTimer.Stop()

                ' Add shape to lines in the playfield
                Field.AddShape(MainShape)

                ' Add Shape blockcount to score
                mScore += MainShape.Count

                ' Start with a new shape
                Start()
            End If
        End If
    End Sub

    Public Sub Rotate(ByVal direction As Graphics.Shape.ShapeRotate)
        ' Rotate shape in the specified direction

        ' Exit when Game Over
        If IsGameOver Then Exit Sub

        ' Exit if game is in Pause
        If mPause Then Exit Sub

        ' Validate before rotating
        If ValidateRotate(MainShape, direction) Then
            MainShape.Rotate(direction)

            ' Send that GameArea, can be redraw
            RaiseEvent Redraw(New Region(New Rectangle(mX, mY, (mWidth * mBlockSize), (mHeight * mBlockSize))))
        End If
    End Sub

    '==== Properties

    Public ReadOnly Property Width() As Integer
        Get
            ' Return Pixels
            ' +5 because of the spare shapes
            Return (mWidth + 5) * mBlockSize
        End Get
    End Property

    Public ReadOnly Property Height() As Integer
        Get
            ' Return Pixels
            Return mHeight * mBlockSize
        End Get
    End Property

    Public ReadOnly Property X() As Integer
        Get
            Return mX
        End Get
    End Property

    Public ReadOnly Property Y() As Integer
        Get
            Return mY
        End Get
    End Property

    Public Property Level() As Integer
        Get
            Return mLevel
        End Get
        Set(ByVal Value As Integer)
            If Value < 1 Or Value > 10 Then Throw New Exception("Only levels 1 to 10")

            mLevel = Value
        End Set
    End Property

    Public ReadOnly Property LinesCleared() As Integer
        Get
            Return mLinesCleared
        End Get
    End Property

    Public ReadOnly Property Score() As Integer
        Get
            Return mScore
        End Get
    End Property

    Public Property Pause() As Boolean
        Get
            Return mPause
        End Get
        Set(ByVal Value As Boolean)
            mPause = Value
        End Set
    End Property
End Class
