Creating your own Tetris game with VB.NET - Part 3

Introduction

If you have been following this series up to this point, I know what you are thinking: When are we going to start with the physical shapes and game play? Well, wait no more :)  In this part of my series, I am going to cover the whole creation of all the shapes, and the game playing methods. if you haven't yet read the previous 2 parts of this article series, I suggest going through them now, as this part is where everything happens!

Logic / Explanation

As mentioned in the first part, Tetris makes use of tetrominoes. A tetromino, also spelled tetramino or tetrimino, is a geometric shape composed of four squares, connected orthogonally. This is a particular type of polyomino, just like dominoes and pentominoes. Sometimes the term is generalized to apply to configurations of four orthogonally connected cubes.  There are seven possible shapes :

Shape Picture
I
O
T
L
J
S
Z

We are going to create these shapes now.  We will also make each shape move and change, so that each shape behaves as it should, in any other Tetris game.  With the help of pressing some keys during game play, the shapes must be flipped over, or turned, but more on that a little bit later, we should create the shapes first, so let us now start.

We do not need to change or add anything on our Form's design, so we can start with the code directly.

Coding

The first thing we have to do is to create our Shapes class.  Add a new class to your project, and name it clsTetShapes

clsTetShapes

Add the following declarations to your class :

    Public rctShape() As Rectangle 'Shape

    Public blnMoving As Boolean 'Is The Shape Moving
    Private blnIsNextShape As Boolean = False 'Image In The Next Shape Window

    Private intStartX As Integer 'Starting Horz Loc
    Private intCurrShape As Integer = 1 'Current Shape
    Private intCurrShapePos As Integer = 1 'Shape Location
    Private intBlockX(4) As Integer 'Each Part Of Tetri, Horz
    Private intBlockY(4) As Integer 'Each part Of Tetri, Vert
    Private intXPos(4) As Integer 'Starting Horz Loc
    Private intPanelWidth As Integer 'Panel Width
    Private intPanelHeight As Integer 'Panel height

These variables are used throughout the class. The width & height of the drawing area(s), the size of the shapes are all stored in the above variables

Add the constructor

    Public Sub New(ByVal intShapeType As Integer, ByVal intScreenW As Integer, ByVal intScreenH As Integer, ByVal blnNextShape As Boolean)

        intStartX = CInt((intScreenW - 1) / 2) 'Start In Center Of X Axis

        intPanelWidth = intScreenW 'Canvas Width
        intPanelHeight = intScreenH 'Canvas Height

        blnIsNextShape = blnNextShape ' Is The Next Shape Shown

        intCurrShape = intShapeType 'Set Current Shape

        ShapeStart() 'Call To ShapeStart Gives Initial values To All Blocks Of All Shapes

    End Sub

In the constructor, we obtain the width of our game area, get a random shape, and calls the ShapeStart sub procedure which basically creates our shapes

The next sub procedure, Build is used to build the four blocks of each shape, depending on which shape we are busy with, enter the following:

    Private Sub Build(ByVal intShapeType As Integer)

        rctShape = New Rectangle(4) {} 'Each Block Of Shape

        'Create 4 Blocks 10 x 10
        rctShape(0) = New Rectangle(intBlockX(0), intBlockY(0), 10, 10)
        rctShape(1) = New Rectangle(intBlockX(1), intBlockY(1), 10, 10)
        rctShape(2) = New Rectangle(intBlockX(2), intBlockY(2), 10, 10)
        rctShape(3) = New Rectangle(intBlockX(3), intBlockY(3), 10, 10)

    End Sub

We utilise the rctShape array, and give each of the four blocks a starting value

Creating your own Tetris game with VB.NET - Part 3

The following three (3) methods handles the shape's movement, left, right, and down. Let us add all three methods now :

    Public Function MoveDown(ByVal intMovePixels As Integer, ByVal rctGameGrid()() As Rectangle) As Rectangle()
        Dim blnMovable As Boolean = True 'Is The Shape Movable?

        Dim j As Integer 'Loop Counter

        ' Check To See If Any X Positions Will Go Over The Edges
        For j = 0 To 3
            If intBlockY(j) + 10 + intMovePixels > intPanelHeight - 1 Then
                blnMovable = False
                blnMoving = False
                Exit For
            End If
        Next j

        If blnMovable Then

            ' Shape Hasn't Reached The Bottom Yet, See If It Will Hit A
            ' Stationary Block 
            Dim k As Integer
            For k = 0 To 3
                If [Decimal].Remainder(intBlockY(k), 10) = 0 And intBlockY(k) >= 0 Then
                    If Not rctGameGrid(CInt((intBlockY(k) / 10 + 1)))(CInt((intBlockX(k) / 10))).IsEmpty Then

                        ' Shape Is About To Enter A Blocked Area
                        blnMovable = False
                        blnMoving = False
                        Exit For
                    End If
                End If
            Next k
        End If

        If blnMovable Then
            Dim i As Integer
            For i = 0 To 3
                intBlockY(i) += intMovePixels
            Next i
        End If
        Build(intCurrShape)
        Return rctShape
    End Function

    Public Function MoveLeft(ByVal intMovePixels As Integer, ByVal rctGameGrid()() As Rectangle) As Rectangle()
        Dim blnMovable As Boolean = True
        Dim intYPos(4) As Integer

        ' Set X Pos To Be Out Of The Range In Our Panel
        Dim intFurthestX As Integer = intPanelWidth

        Dim j As Integer

        ' Check To See If Any X Positions Will Go Over The Edge Or Try And Enter A 
        ' Blocked Area We Need The Leftmost Block(s) To Test Could Be 1,2,3, or 4
        For j = 0 To 3
            If intBlockX(j) <= intFurthestX Then
                intFurthestX = intBlockX(j)
                intYPos(j) = intBlockY(j)
                If [Decimal].Remainder(intBlockY(j), 10) <> 0 Then
                    intYPos(j) += 5
                End If
            End If
            If intBlockX(j) - intMovePixels < 0 Then
                blnMovable = False
                Exit For
            End If
        Next j

        If blnMovable Then
            Dim i As Integer
            For i = 0 To 3
                If intYPos(i) >= 0 Then
                    If Not rctGameGrid(CInt((intYPos(i) / 10)))(CInt(((intFurthestX - 10) / 10))).IsEmpty Then
                        ' Shape Is About To Enter A Blocked Area
                        blnMovable = False
                        Exit For
                    End If
                End If
            Next i
        End If

        If blnMovable Then
            Dim i As Integer
            For i = 0 To 3
                intBlockX(i) -= intMovePixels
            Next i
        End If
        Build(intCurrShape)
        Return rctShape
    End Function

    Public Function MoveRight(ByVal intMovePixels As Integer, ByVal rctGameGrid()() As Rectangle) As Rectangle()
        Dim blnMovable As Boolean = True

        Dim intYPos(4) As Integer

        ' Set X Pos To Be Out Of The Range In Our Panel
        Dim intFurthestX As Integer = 0

        ' Check To See If Any X Positions Will Go Over The Edge
        Dim j As Integer

        For j = 0 To 3
            If intBlockX(j) >= intFurthestX Then
                intFurthestX = intBlockX(j)
                intYPos(j) = intBlockY(j)
                If [Decimal].Remainder(intBlockY(j), 10) <> 0 Then
                    intYPos(j) += 5
                End If
            End If

            If intBlockX(j) + intMovePixels + 10 >= intPanelWidth Then
                blnMovable = False
                Exit For
            End If

        Next j

        If blnMovable Then
            Dim i As Integer

            For i = 0 To 3
                If intYPos(i) >= 0 Then
                    If Not rctGameGrid(CInt(intYPos(i) / 10))(CInt(((intFurthestX + 10) / 10))).IsEmpty Then

                        ' Shape Is About To Enter A Blocked Area
                        blnMovable = False
                        Exit For
                    End If
                End If
            Next i
        End If

        If blnMovable Then
            Dim i As Integer
            For i = 0 To 3
                intBlockX(i) += intMovePixels
            Next i
        End If

        Build(intCurrShape)

        Return rctShape

    End Function

Whichever tetri is displaying, must act as a unit. Yes, all four blocks of each shape is separate, it is our job to make use of the For loops to move each part along with the other 3 blocks. The trick is in the For Loops, as I said. Because we loop through each element of the rctBlock X or Y array, we can move each block separately the specified amount of pixels

Creating your own Tetris game with VB.NET - Part 3

The next Sub procedure Flips the shapes, let us add it:

    Public Function Flip(ByVal strDirection As String, ByVal rctGameGrid()() As Rectangle) As Rectangle()

        Dim blnMovable As Boolean = True

        If strDirection = "right" Then
            intCurrShapePos += 1
            If intCurrShapePos > 4 Then
                intCurrShapePos = 1
            End If
        End If

        If strDirection = "left" Then
            intCurrShapePos -= 1
            If intCurrShapePos < 1 Then
                intCurrShapePos = 4 'Was 4
            End If
        End If

        SetShapePos()
        Build(intCurrShape)

        ' Before Returning The Shape, See If It Is Within The Bounds Of The Panel
        Dim recGameArea As New Rectangle(0, 0, intPanelWidth, intPanelHeight)

        Dim intYPositions(4) As Integer
        Dim i As Integer

        For i = 0 To 3
            If Not recGameArea.Contains(rctShape(i)) Then
                blnMovable = False
                Exit For
            End If

            ' See If The Shape Is Going To Collide With Any Other
            ' Stationary Objects

            intYPositions(i) = intBlockY(i)
            If [Decimal].Remainder(intBlockY(i), 10) <> 0 Then
                intYPositions(i) += 5
            End If

            If Not rctGameGrid(CInt((intYPositions(i) / 10)))(CInt((intBlockX(i) / 10))).IsEmpty Then

                ' Shape Is About To Enter A Blocked Area
                blnMovable = False
                Exit For
            End If
        Next i

        If Not blnMovable Then
            ' Rollback
            If strDirection = "right" Then
                intCurrShapePos -= 1
                If intCurrShapePos < 1 Then
                    intCurrShapePos = 4
                End If

                SetShapePos()
                Build(intCurrShape)
            End If

            If strDirection = "left" Then
                intCurrShapePos += 1
                If intCurrShapePos > 4 Then
                    intCurrShapePos = 1
                End If

                SetShapePos()
                Build(intCurrShape)
            End If
        End If

        Return rctShape

    End Function

This sub gets called whenever K or A is pressed during game play. This sub also makes use of the SetShapePos method to turn each shape, according to whichever key was pressed. When Flipping a graphic object, it means that you are actually creating a mirror image from it, and it returns the mirror image. We can flip the shape left or right

Next up we have the ShapeStart method. This method, although long, sets the start-up positions for whichever shape in the main game window, or in the Preview window

    Private Sub ShapeStart()

        If Not blnIsNextShape Then 'If Shape Shown In Main Game Window
            Select Case intCurrShape

                Case 1 'T Shape - Blue
                    intBlockX(0) = intStartX
                    intBlockY(0) = -10
                    intBlockX(1) = intStartX - 10
                    intBlockY(1) = -10
                    intBlockX(2) = intStartX + 10
                    intBlockY(2) = -10
                    intBlockX(3) = intStartX
                    intBlockY(3) = -20

                Case 2 'L Shape - Red
                    intBlockX(0) = intStartX
                    intBlockY(0) = -10
                    intBlockX(1) = intStartX - 10
                    intBlockY(1) = -10
                    intBlockX(2) = intStartX + 10
                    intBlockY(2) = -10
                    intBlockX(3) = intStartX + 10
                    intBlockY(3) = -20

                Case 3 'J Shape - Green
                    intBlockX(0) = intStartX
                    intBlockY(0) = -10
                    intBlockX(1) = intStartX - 10
                    intBlockY(1) = -10
                    intBlockX(2) = intStartX + 10
                    intBlockY(2) = -10
                    intBlockX(3) = intStartX - 10
                    intBlockY(3) = -20

                Case 4 'O Shape - Yellow
                    intBlockX(0) = intStartX
                    intBlockY(0) = -10
                    intBlockX(1) = intStartX + 10
                    intBlockY(1) = -10
                    intBlockX(2) = intStartX
                    intBlockY(2) = -20
                    intBlockX(3) = intStartX + 10
                    intBlockY(3) = -20

                Case 5 'I Shape - Brown
                    intBlockX(0) = intStartX
                    intBlockY(0) = -10
                    intBlockX(1) = intStartX
                    intBlockY(1) = -20
                    intBlockX(2) = intStartX
                    intBlockY(2) = -30
                    intBlockX(3) = intStartX
                    intBlockY(3) = -40

                Case 6 'S Shape - Orange
                    intBlockX(0) = intStartX - 10
                    intBlockY(0) = -10
                    intBlockX(1) = intStartX
                    intBlockY(1) = -10
                    intBlockX(2) = intStartX
                    intBlockY(2) = -20
                    intBlockX(3) = intStartX + 10
                    intBlockY(3) = -20

                Case 7 'Z Shape - Purple
                    intBlockX(0) = intStartX + 10
                    intBlockY(0) = -10
                    intBlockX(1) = intStartX
                    intBlockY(1) = -10
                    intBlockX(2) = intStartX
                    intBlockY(2) = -20
                    intBlockX(3) = intStartX - 10
                    intBlockY(3) = -20

                Case Else
            End Select

        Else

            Select Case intCurrShape 'If Shape Shown In preview Window

                Case 1 'T
                    intBlockX(0) = intStartX - 5
                    intBlockY(0) = 35
                    intBlockX(1) = intStartX - 15
                    intBlockY(1) = 35
                    intBlockX(2) = intStartX + 5
                    intBlockY(2) = 35
                    intBlockX(3) = intStartX - 5
                    intBlockY(3) = 25

                Case 2 'L
                    intBlockX(0) = intStartX - 5
                    intBlockY(0) = 35
                    intBlockX(1) = intStartX - 15
                    intBlockY(1) = 35
                    intBlockX(2) = intStartX + 5
                    intBlockY(2) = 35
                    intBlockX(3) = intStartX + 5
                    intBlockY(3) = 25

                Case 3 'J
                    intBlockX(0) = intStartX - 5
                    intBlockY(0) = 35
                    intBlockX(1) = intStartX - 15
                    intBlockY(1) = 35
                    intBlockX(2) = intStartX + 5
                    intBlockY(2) = 35
                    intBlockX(3) = intStartX - 15
                    intBlockY(3) = 25

                Case 4 'O
                    intBlockX(0) = intStartX - 10
                    intBlockY(0) = 35
                    intBlockX(1) = intStartX
                    intBlockY(1) = 35
                    intBlockX(2) = intStartX - 10
                    intBlockY(2) = 25
                    intBlockX(3) = intStartX
                    intBlockY(3) = 25

                Case 5 'I
                    intBlockX(0) = intStartX - 5
                    intBlockY(0) = 45
                    intBlockX(1) = intStartX - 5
                    intBlockY(1) = 35
                    intBlockX(2) = intStartX - 5
                    intBlockY(2) = 25
                    intBlockX(3) = intStartX - 5
                    intBlockY(3) = 15

                Case 6 'S
                    intBlockX(0) = intStartX - 10
                    intBlockY(0) = 45
                    intBlockX(1) = intStartX
                    intBlockY(1) = 45
                    intBlockX(2) = intStartX
                    intBlockY(2) = 35
                    intBlockX(3) = intStartX + 10
                    intBlockY(3) = 35

                Case 7 'Z
                    intBlockX(0) = intStartX + 10
                    intBlockY(0) = 45
                    intBlockX(1) = intStartX
                    intBlockY(1) = 45
                    intBlockX(2) = intStartX
                    intBlockY(2) = 35
                    intBlockX(3) = intStartX - 10
                    intBlockY(3) = 35

                Case Else
            End Select

            Build(intCurrShape)

        End If
    End Sub

Actually quite simple when you look at it, once you have the basic location for the starting block, you can easily arrange all other blocks into the shape you want, because all blocks are the same size

Creating your own Tetris game with VB.NET - Part 3

The next simple function, simply gets the next shape in the queue, to display it in the Preview window

    Public Function GetShape() As Rectangle()
        Return rctShape ' Return Next Shape
    End Function

The last sub we are going to do for the clsTetShapes class is SetShapePos sub, let us add it quickly :

    Private Sub SetShapePos()
        Select Case intCurrShape
            Case 1 'T

                Select Case intCurrShapePos

                    Case 1
                        intBlockX(0) = intBlockX(0)
                        intBlockY(0) = intBlockY(0)
                        intBlockX(1) = intBlockX(0) - 10
                        intBlockY(1) = intBlockY(0)
                        intBlockX(2) = intBlockX(0) + 10
                        intBlockY(2) = intBlockY(0)
                        intBlockX(3) = intBlockX(0)
                        intBlockY(3) = intBlockY(0) - 10

                    Case 2
                        intBlockX(0) = intBlockX(0)
                        intBlockY(0) = intBlockY(0)
                        intBlockX(1) = intBlockX(0)
                        intBlockY(1) = intBlockY(0) - 10
                        intBlockX(2) = intBlockX(0)
                        intBlockY(2) = intBlockY(0) + 10
                        intBlockX(3) = intBlockX(0) + 10
                        intBlockY(3) = intBlockY(0)

                    Case 3
                        intBlockX(0) = intBlockX(0)
                        intBlockY(0) = intBlockY(0)
                        intBlockX(1) = intBlockX(0) + 10
                        intBlockY(1) = intBlockY(0)
                        intBlockX(2) = intBlockX(0) - 10
                        intBlockY(2) = intBlockY(0)
                        intBlockX(3) = intBlockX(0)
                        intBlockY(3) = intBlockY(0) + 10

                    Case 4
                        intBlockX(0) = intBlockX(0)
                        intBlockY(0) = intBlockY(0)
                        intBlockX(1) = intBlockX(0)
                        intBlockY(1) = intBlockY(0) + 10
                        intBlockX(2) = intBlockX(0)
                        intBlockY(2) = intBlockY(0) - 10
                        intBlockX(3) = intBlockX(0) - 10
                        intBlockY(3) = intBlockY(0)

                    Case Else
                End Select

            Case 2 'L
                Select Case intCurrShapePos

                    Case 1
                        intBlockX(0) = intBlockX(0)
                        intBlockY(0) = intBlockY(0)
                        intBlockX(1) = intBlockX(0) - 10
                        intBlockY(1) = intBlockY(0)
                        intBlockX(2) = intBlockX(0) + 10
                        intBlockY(2) = intBlockY(0)
                        intBlockX(3) = intBlockX(0) + 10
                        intBlockY(3) = intBlockY(0) - 10

                    Case 2
                        intBlockX(0) = intBlockX(0)
                        intBlockY(0) = intBlockY(0)
                        intBlockX(1) = intBlockX(0)
                        intBlockY(1) = intBlockY(0) - 10
                        intBlockX(2) = intBlockX(0)
                        intBlockY(2) = intBlockY(0) + 10
                        intBlockX(3) = intBlockX(0) + 10
                        intBlockY(3) = intBlockY(0) + 10

                    Case 3
                        intBlockX(0) = intBlockX(0)
                        intBlockY(0) = intBlockY(0)
                        intBlockX(1) = intBlockX(0) + 10
                        intBlockY(1) = intBlockY(0)
                        intBlockX(2) = intBlockX(0) - 10
                        intBlockY(2) = intBlockY(0)
                        intBlockX(3) = intBlockX(0) - 10
                        intBlockY(3) = intBlockY(0) + 10

                    Case 4
                        intBlockX(0) = intBlockX(0)
                        intBlockY(0) = intBlockY(0)
                        intBlockX(1) = intBlockX(0)
                        intBlockY(1) = intBlockY(0) + 10
                        intBlockX(2) = intBlockX(0)
                        intBlockY(2) = intBlockY(0) - 10
                        intBlockX(3) = intBlockX(0) - 10
                        intBlockY(3) = intBlockY(0) - 10
                    Case Else
                End Select

            Case 3 'J
                Select Case intCurrShapePos

                    Case 1
                        intBlockX(0) = intBlockX(0)
                        intBlockY(0) = intBlockY(0)
                        intBlockX(1) = intBlockX(0) - 10
                        intBlockY(1) = intBlockY(0)
                        intBlockX(2) = intBlockX(0) + 10
                        intBlockY(2) = intBlockY(0)
                        intBlockX(3) = intBlockX(0) - 10
                        intBlockY(3) = intBlockY(0) - 10

                    Case 2
                        intBlockX(0) = intBlockX(0)
                        intBlockY(0) = intBlockY(0)
                        intBlockX(1) = intBlockX(0)
                        intBlockY(1) = intBlockY(0) - 10
                        intBlockX(2) = intBlockX(0)
                        intBlockY(2) = intBlockY(0) + 10
                        intBlockX(3) = intBlockX(0) + 10
                        intBlockY(3) = intBlockY(0) - 10

                    Case 3
                        intBlockX(0) = intBlockX(0)
                        intBlockY(0) = intBlockY(0)
                        intBlockX(1) = intBlockX(0) + 10
                        intBlockY(1) = intBlockY(0)
                        intBlockX(2) = intBlockX(0) - 10
                        intBlockY(2) = intBlockY(0)
                        intBlockX(3) = intBlockX(0) + 10
                        intBlockY(3) = intBlockY(0) + 10

                    Case 4
                        intBlockX(0) = intBlockX(0)
                        intBlockY(0) = intBlockY(0)
                        intBlockX(1) = intBlockX(0)
                        intBlockY(1) = intBlockY(0) + 10
                        intBlockX(2) = intBlockX(0)
                        intBlockY(2) = intBlockY(0) - 10
                        intBlockX(3) = intBlockX(0) - 10
                        intBlockY(3) = intBlockY(0) + 10

                    Case Else
                End Select

                '4 Is O Shape - Already Square Won't See Anything

            Case 5  'I
                Select Case intCurrShapePos

                    Case 1
                        intBlockX(0) = intBlockX(0)
                        intBlockY(0) = intBlockY(0)
                        intBlockX(1) = intBlockX(0) + 10
                        intBlockY(1) = intBlockY(0)
                        intBlockX(2) = intBlockX(0) + 20
                        intBlockY(2) = intBlockY(0)
                        intBlockX(3) = intBlockX(0) + 30
                        intBlockY(3) = intBlockY(0)

                    Case 2
                        intBlockX(0) = intBlockX(0)
                        intBlockY(0) = intBlockY(0)
                        intBlockX(1) = intBlockX(0)
                        intBlockY(1) = intBlockY(0) - 10
                        intBlockX(2) = intBlockX(0)
                        intBlockY(2) = intBlockY(0) - 20
                        intBlockX(3) = intBlockX(0)
                        intBlockY(3) = intBlockY(0) - 30

                    Case 3
                        intBlockX(0) = intBlockX(0)
                        intBlockY(0) = intBlockY(0)
                        intBlockX(1) = intBlockX(0) + 10
                        intBlockY(1) = intBlockY(0)
                        intBlockX(2) = intBlockX(0) + 20
                        intBlockY(2) = intBlockY(0)
                        intBlockX(3) = intBlockX(0) + 30
                        intBlockY(3) = intBlockY(0)

                    Case 4
                        intBlockX(0) = intBlockX(0)
                        intBlockY(0) = intBlockY(0)
                        intBlockX(1) = intBlockX(0)
                        intBlockY(1) = intBlockY(0) - 10
                        intBlockX(2) = intBlockX(0)
                        intBlockY(2) = intBlockY(0) - 20
                        intBlockX(3) = intBlockX(0)
                        intBlockY(3) = intBlockY(0) - 30

                    Case Else
                End Select

            Case 6 'S
                Select Case intCurrShapePos

                    Case 1
                        intBlockX(0) = intBlockX(0)
                        intBlockY(0) = intBlockY(0)
                        intBlockX(1) = intBlockX(0)
                        intBlockY(1) = intBlockY(0) + 10
                        intBlockX(2) = intBlockX(0) + 10
                        intBlockY(2) = intBlockY(0) + 10
                        intBlockX(3) = intBlockX(0) + 10
                        intBlockY(3) = intBlockY(0) + 20

                    Case 2
                        intBlockX(0) = intBlockX(0)
                        intBlockY(0) = intBlockY(0)
                        intBlockX(1) = intBlockX(0) - 10
                        intBlockY(1) = intBlockY(0)
                        intBlockX(2) = intBlockX(0)
                        intBlockY(2) = intBlockY(0) - 10
                        intBlockX(3) = intBlockX(0) + 10
                        intBlockY(3) = intBlockY(0) - 10

                    Case 3
                        intBlockX(0) = intBlockX(0)
                        intBlockY(0) = intBlockY(0)
                        intBlockX(1) = intBlockX(0)
                        intBlockY(1) = intBlockY(0) + 10
                        intBlockX(2) = intBlockX(0) + 10
                        intBlockY(2) = intBlockY(0) + 10
                        intBlockX(3) = intBlockX(0) + 10
                        intBlockY(3) = intBlockY(0) + 20

                    Case 4
                        intBlockX(0) = intBlockX(0)
                        intBlockY(0) = intBlockY(0)
                        intBlockX(1) = intBlockX(0) - 10
                        intBlockY(1) = intBlockY(0)
                        intBlockX(2) = intBlockX(0)
                        intBlockY(2) = intBlockY(0) - 10
                        intBlockX(3) = intBlockX(0) + 10
                        intBlockY(3) = intBlockY(0) - 10

                    Case Else
                End Select

            Case 7 'Z
                Select Case intCurrShapePos

                    Case 1
                        intBlockX(0) = intBlockX(0)
                        intBlockY(0) = intBlockY(0)
                        intBlockX(1) = intBlockX(0)
                        intBlockY(1) = intBlockY(0) - 10
                        intBlockX(2) = intBlockX(0) - 10
                        intBlockY(2) = intBlockY(0) - 10
                        intBlockX(3) = intBlockX(0) - 10
                        intBlockY(3) = intBlockY(0) - 20

                    Case 2
                        intBlockX(0) = intBlockX(0)
                        intBlockY(0) = intBlockY(0)
                        intBlockX(1) = intBlockX(0) + 10
                        intBlockY(1) = intBlockY(0)
                        intBlockX(2) = intBlockX(0)
                        intBlockY(2) = intBlockY(0) + 10
                        intBlockX(3) = intBlockX(0) - 10
                        intBlockY(3) = intBlockY(0) + 10

                    Case 3
                        intBlockX(0) = intBlockX(0)
                        intBlockY(0) = intBlockY(0)
                        intBlockX(1) = intBlockX(0)
                        intBlockY(1) = intBlockY(0) - 10
                        intBlockX(2) = intBlockX(0) - 10
                        intBlockY(2) = intBlockY(0) - 10
                        intBlockX(3) = intBlockX(0) - 10
                        intBlockY(3) = intBlockY(0) - 20

                    Case 4
                        intBlockX(0) = intBlockX(0)
                        intBlockY(0) = intBlockY(0)
                        intBlockX(1) = intBlockX(0) + 10
                        intBlockY(1) = intBlockY(0)
                        intBlockX(2) = intBlockX(0)
                        intBlockY(2) = intBlockY(0) + 10
                        intBlockX(3) = intBlockX(0) - 10
                        intBlockY(3) = intBlockY(0) + 10

                    Case Else
                End Select

            Case Else
        End Select

    End Sub

Stop crying, please! It is not that bad! :) We followed the same principle as we did in the ShapeStart sub. The only difference now is that because we have 4 possible variations of the current shape, we must now set each variation's shape's coordinates properly. If you do not understand what I mean about the four possible variations, I mean that you can turn the shape clockwise or counter clockwise ( depending on whether A or K was pressed), and to cater for that shape change, we must set each block's location in each shape accordingly.

Creating your own Tetris game with VB.NET - Part 3

Phew! That was some coding wasn't it? Bad news, we're still not done. Let us now proceed to all the necessary methods and objects we need to add to frmTet's code. Open the Code for frmTet, and add the following variables into the General Declarations area

    Private shpShape As clsTetShapes 'Current Shape
    Private shpNextShape As clsTetShapes 'Next Shape

    Private rndShapeType As New Random 'Random Shape

    Private rctGame()() As Rectangle 'Game Rectangle
    Private rctShape As Rectangle() 'Shape Rectangle

    Private blnGamePaused As Boolean 'Game Paused?
    Private blnRowFull As Boolean 'Row Full?

    Private intNextShapeType As Integer 'Next Shape Type
    Private intShapeType As Integer 'Current Shape Type

Here, we just created all the necessary variables in order to make use of our clsTetShapes class efficiently

Add the Following sub procedure to frmTet:

    Private Sub MainGame()

        Dim g As Graphics = scrGame.GetGraphics() 'Allow Drawing

        arrGridBrushes = grdGame.GetGridBrushes() 'Get All Brush Colours

        arrColours = grdGame.GetColours() 'Get All Block Colours

        rctGame = grdGame.GetGrid() 'Set Up Grid

        scrGame.ClearScreen() 'Clear Current Screen - To Redraw

        ' Draw The Stationary Shapes First
        Dim i As Integer
        For i = 0 To intNoOfRows - 1
            Dim k As Integer

            For k = 0 To intNoOfCols - 1

                If Not grdGame.IsLocEmpty(i, k) Then 'Is Row Full?
                    g.FillRectangle(arrGridBrushes(i)(k), rctGame(i)(k))
                    g.DrawRectangle(New Pen(Color.White, 1), rctGame(i)(k))
                End If

            Next k

        Next i

        ' Draw Moving Shape
        Dim j As Integer

        For j = 0 To rctShape.Length - 1
            g.FillRectangle(arrColours((intShapeType - 1)), rctShape(j))
            g.DrawRectangle(New Pen(Color.White, 1), rctShape(j))
        Next j


        scrGame.BufferImage() 'Double Buffer

    End Sub

Aptly named, this sub controls the whole flow of our game in progress. Most importantly, it draws all the stationary shapes ( the shapes standing still ), and then the moving shape

The next function, named NextShape is next

    Private Sub NextShape()

        Dim g As Graphics = scrPreview.GetGraphics() 'Allow Drawing In Preview

        Dim rctNextShape() As Rectangle 'Set Up Next Shape

        rctNextShape = shpNextShape.GetShape() 'Get Next Shape

        arrColours = grdGame.GetColours() 'Get Next Shape Colours

        scrPreview.ClearScreen() ' Clear Screen

        Dim j As Integer

        For j = 0 To 3 'Draw Shape
            g.FillRectangle(arrColours((intNextShapeType - 1)), rctNextShape(j))
            g.DrawRectangle(New Pen(Color.White, 1), rctNextShape(j))
        Next j

        scrPreview.BufferImage()

    End Sub

The above sub simply gets the next shape in the queue and draws it in the Preview window

    Private Function GetShapeType() As Integer
        Dim intShapeType As Integer 'Shape Type

        Do 'Randomly Pick Shape type
            intShapeType = rndShapeType.Next(8)
        Loop While intShapeType = 0

        Return intShapeType

    End Function

The GetShapeType function randomly picks a shape out of the seven possible shapes, and then return it to the calling function

Game Over is next:

    Private Sub GameOver()

        Dim gOver As Graphics = scrSplash.GetGraphics() 'Make Use Of Splash Graphic Area

        scrSplash.ClearScreen() 'Clear Whatever Was Drawn

        'Draw GAME OVER
        gOver.DrawString("GAME OVER", New Font("Courier", 18), New SolidBrush(Color.Red), 5, 100)

        scrSplash.BufferImage()
    End Sub

This sub simply draws the string GAME OVER when your game is over

Creating your own Tetris game with VB.NET - Part 3

    Private Sub UpdateScore(ByVal intRowNum As Integer)
        intLevelRows += 1 'Increment Level Rows
        intTotalRows += 1 'Increment Total Rows

        Dim intReverseRow As Integer = 30 - intRowNum 'Get "UnUsed Rows"

        'UnUsed Rows * Level Rows * Level * 10
        lngScore = lngScore + intReverseRow * intLevelRows * intLevel * 10

        lblTetScore.Text = lngScore.ToString() 'Update Score

        If intLevelRows = 10 Then 'If 10 Rows Done In Level, Increment Level
            UpdateLevel()
            intLevelRows = 0
        End If

        lblTetRows.Text = intTotalRows.ToString() 'Show How Many Rows "Won" So Far

    End Sub
    
    Private Sub UpdateLevel()
        intLevel += 1 'Increment Level

        If intGameSpeed > 10 Then 'Increase Game Speed
            intGameSpeed -= 10
        End If

        lblTetLevel.Text = intLevel.ToString() 'Show Current Level

    End Sub

The previous 2 subs simply updates the score and the level. Once 10 rows have been completed, a new level starts, and obviously, the timer's speed increases

    Private Sub frmTet_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles MyBase.KeyDown

        Dim strKeyPress As String = Nothing
        strKeyPress = e.KeyCode.ToString() 'Gets Key Pressed

        If Not blnGameOver Then 'Still Playing?
            Select Case strKeyPress.ToUpper()

                Case "Z" 'Move Left
                    If shpShape.blnMoving Then
                        rctShape = shpShape.MoveLeft(10, grdGame.GetGrid())
                    End If

                Case "M" 'Move Right
                    If shpShape.blnMoving Then
                        rctShape = shpShape.MoveRight(10, grdGame.GetGrid())
                    End If

                Case "K" 'Flip Right
                    If shpShape.blnMoving Then
                        rctShape = shpShape.Flip("right", grdGame.GetGrid())
                    End If

                Case "A" 'Flip Left
                    If shpShape.blnMoving Then
                        rctShape = shpShape.Flip("left", grdGame.GetGrid())
                    End If

                Case "Q" 'Stop, Reset Everything
                    tmrTet.Stop()
                    SetUp()
                    Splash()

                Case "P" 'Pause
                    If Not blnGamePaused Then
                        tmrTet.Stop()
                        blnGamePaused = True
                    Else
                        tmrTet.Start()
                        blnGamePaused = False
                    End If

                Case "SPACE" 'Drop Shape
                    If shpShape.blnMoving Then
                        tmrTet.Interval = intDropRate
                        blnDropped = True
                    End If
                Case Else

            End Select

        Else

            Select Case strKeyPress.ToUpper()

                Case "RETURN" 'Start When Enter Pressed

                    ' Setup Game And Set Default Properties
                    SetUp()
                    intShapeType = GetShapeType()

                    shpShape = New clsTetShapes(intShapeType, scrGame.ScreenWidth, scrGame.ScreenHeight, False)

                    intNextShapeType = GetShapeType()

                    shpNextShape = New clsTetShapes(intNextShapeType, scrPreview.ScreenWidth, scrPreview.ScreenHeight, True)

                    shpShape.blnMoving = True
                    blnGameOver = False

                    tmrTet.Interval = intGameSpeed

                    tmrTet.Enabled = True
                    tmrTet.Start()
                    NextShape()

                Case Else
            End Select
        End If
    End Sub

The KeyDown event determines which key has been pressed and acts accordingly

The last event we are adding to our form is the Timer's tick event, which makes everything happen, let us have a look:

    Private Sub tmrTet_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles tmrTet.Tick

        If shpShape.blnMoving Then 'IF Shape Is Still Moving, Move Down
            rctShape = shpShape.MoveDown(intDropRate, grdGame.GetGrid())
            MainGame()
        Else

            'Current Shape Stopped Moving
            Dim intXCoordinate As Integer
            Dim intYCoordinate As Integer
            Dim i As Integer

            'Is Shape Within Game Area? If Not, Game Is Over
            For i = 0 To 3
                If Not rctScreen.Contains(rctShape(i)) Then
                    blnGameOver = True
                    Exit For
                End If
            Next i

            If Not blnGameOver Then

                Dim intYCoordinates(4) As Integer
                'Paint Shape's Final Position

                For i = 0 To 3 'Gets Coordinates
                    intXCoordinate = rctShape(i).X
                    intYCoordinate = rctShape(i).Y
                    intYCoordinates(i) = intYCoordinate / 10

                    'Copy Shape's Position Into Game Grid
                    grdGame.SetLoc(intYCoordinate / 10, intXCoordinate / 10, rctShape(i), intShapeType)
                Next i

                'Sort Array Of Y Coordinates So That We Can Go From Small To Large
                'Ensures That We Drop Rows Sequentially
                Array.Sort(intYCoordinates)

                For i = 0 To 3
                    blnRowFull = True

                    'Check If Shape Causes An Entire Row To Fill
                    'If It Does Then We Need To Eliminate The Row And Drop The Rest Down
                    Dim j As Integer

                    For j = 0 To intNoOfCols - 1
                        If grdGame.IsLocEmpty(intYCoordinates(i), j) Then
                            blnRowFull = False
                            Exit For
                        Else
                            blnRowFull = True
                        End If
                    Next j

                    If blnRowFull Then
                        'Drop Row And Fill From Next Row Down

                        Dim k As Integer
                        For k = intYCoordinates(i) To -1 Step -1
                            'Update All Coordinates Of Our Shapes

                            Dim l As Integer
                            For l = 0 To intNoOfCols - 1

                                'Set Value Into Row Below
                                grdGame.DropDown(k, l)
                            Next l
                        Next k

                        'Do Row 0
                        grdGame.FirstRow()

                        'Update Score
                        UpdateScore(intYCoordinates(i))
                    End If
                Next i

                intShapeType = intNextShapeType
                shpShape = New clsTetShapes(intShapeType, scrGame.ScreenWidth, scrGame.ScreenHeight, False)
                intNextShapeType = GetShapeType()
                shpNextShape = New clsTetShapes(intNextShapeType, scrPreview.ScreenWidth, scrPreview.ScreenHeight, True)
                shpShape.blnMoving = True
                NextShape()

                'Reset Game Speed
                tmrTet.Interval = intGameSpeed
                blnDropped = False
            Else
                tmrTet.Stop()
                GameOver()
            End If
        End If
    End Sub

You may be thinking that we are done. Not yet. We just have to edit an already existing sub in clsTetGrid. Let us open up the code for clsTetGrid and edit the DropDown sub to look like the following:

    Public Sub DropDown(ByVal intRowNo As Integer, ByVal intColNo As Integer)

        'CHANGED HERE IN PART 3 - ADDED OR NOT INTROWNO <= 0
        If Not IsLocEmpty(intRowNo - 1, intColNo) Or Not intRowNo <= 0 Then 'Check If Next Row Is Empty

            'Set Current Location Equal To The Same Location Horizontally, But
            'Next Row ( Vertically )
            'Size Is 10
            arrGrids(intRowNo)(intColNo) = New Rectangle(arrGrids((intRowNo - 1))(intColNo).X, arrGrids((intRowNo - 1))(intColNo).Y + 10, 10, 10)

            'Make Sure Gridlines Move Too
            arrGridBrushes(intRowNo)(intColNo) = arrGridBrushes((intRowNo - 1))(intColNo)

        Else 'Next Row Is Not Empty
            arrGrids(intRowNo)(intColNo) = arrGrids((intRowNo - 1))(intColNo)
        End If

    End Sub

It is mostly the same as it was before, I just added the Or Not statement into the equation. Why? because I noticed it threw an exception when the rows are supposed to be cleared. It doesn't now

All the coding is done, for this part. I was planning to do only three parts for this article series, but as luck would have it, I have noticed several minor issues and a major issue, which can be classified as a bug, with this current Beta version. You have to understand that I want to share with you all my experiences while creating this project, and if an error or two creeps in, it is normal, and we'll get it fixed in the next part

Conclusion

Well, this one was hard work, go out and have a beer, it's on me :) We have covered the whole game play experience with this article, and covered the creation of the shapes in this section. What we will do in the next part, is to fix all the known issues ( that I have discovered so far ), and if you find any others, please let me know. Until next time, have fun - but not too much!


About the Author

Hannes du Preez

Hannes du Preez is a Microsoft MVP for Visual Basic. He is a trainer at a South African-based company. He is the co-founder of hmsmp.co.za, a community for South African developers.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

  • You must have javascript enabled in order to post comments.

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

  • As a development and deployment platform, RHEL offers an efficient, scalable, and robust operating environment with certified security and flexible deployment options in physical and virtualized environments. To assess and quantify the business benefits of RHEL, IDC recently conducted in-depth interviews with IT staff members of 21 companies using RHEL servers. The organizations represent a broad range of industries and have an average of 22,700 employees. RHEL servers accounted for 23% of the servers …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds