c语言俄罗斯方块方块图形_玩转图形,第三部分:让我们玩俄罗斯方块!

c语言俄罗斯方块方块图形

Recently, in one of the tech-blogs I usually read, I saw a post about the best-selling video games through history. The first place in the list is for the classic, extremely addictive Tetris.

最近,在我经常阅读的一个技术博客中,我看到了一篇有关历史上最畅销视频游戏的文章。 列表中的第一名是经典,极易上瘾的俄罗斯方块。

Well, a long time ago, in a galaxy far far away, I was a highly-skilled Tetris player. When I was 18 I worked as DJ in a disco-pub at my hometown. There was a Tetris gaming machine, and I spent hours and hours playing on it. Many nights I saw the blocks falling every time I went to bed and closed my eyes.

好吧,很久以前,在一个遥远的星系中,我是一个高技能的俄罗斯方块玩家。 当我18岁时,我在家乡的一家迪斯科酒吧里担任DJ。 有一台俄罗斯方块游戏机,我花了好几个小时在上面玩。 很多晚上,每次我上床睡觉并闭上眼睛时,我都看到砖块掉落了。

It rained some since then, but I still believe that Tetris is one of the best videogames ever made.

从那以后开始下雨了,但是我仍然相信俄罗斯方块是有史以来最好的电子游戏之一。

In 2000, I developed a Tetris ActiveX control for the Planet Source Code site using Visual Basic 6.0. With that control I won some prize in the Planet Source Code monthly contest (some software pack, I don't remember well). You can still see the page (and download the ActiveX control and its source code) here.

在2000年,我使用Visual Basic 6.0为Planet Source Code网站开发了Tetris ActiveX控件。 通过这种控制,我在“ Planet Source Code”每月竞赛中获得了一些奖项(某些软件包,我不太记得)。 您仍然可以在此处查看页面(并下载ActiveX控件及其源代码)。

Now I think the time has come to write a .Net version, so this article presents to you the TetrisBox class: a Vb.net class extremely useful to create highly customizable Tetris games in .Net Winforms environments.

现在我认为是时候编写.Net版本了,所以本文向您介绍TetrisBox类 :一个Vb.net类,对于在.Net Winforms环境中创建高度可自定义的Tetris游戏非常有用。

TetrisBox类下的逻辑 (The logic under the TetrisBox Class)

The TetrisBox class inherits from the System.Windows.Forms.PictureBox control, as it is basically a drawing surface. All the drawing work is done in the overwritten OnPaint method. Because the PictureBox inheritance, it has a graphical UI and can be placed on any Windows Form or a user control.

TetrisBox类继承自System.Windows.Forms.Pictu reBox控件,因为它基本上是一个绘图表面。 所有绘图工作都是通过覆盖的OnPaint方法完成的。 由于PictureBox继承,因此它具有图形用户界面,可以放置在任何Windows窗体或用户控件上。

Internally, the box represents the board game and is logical-divided into cells. The number of cells is defined by the Rows and Columns properties, and the size of each cell is defined by the CellSize property, which uses pixels. So, for example, a board with 20 rows, 10 columns and a CellSize of 25 will have a width of 241 pixels and a height of 481 pixels (that's because adjacent cells share the borders).

在内部,该框代表棋盘游戏,并按逻辑划分为多个单元。 单元格的数量由“ 列”属性定义,每个单元格的大小由“ CellSize”属性定义,该属性使用像素。 因此,例如,具有20行,10列且CellSize为25的板的宽度为241像素,高度为481像素(这是因为相邻的单元格共享边框)。

When a game is running, the control first paints its background (it can be a solid color, a gradient or a picture) and then it draws each cell. The state of each cell is saved in memory using different structures. When the game starts, the board is empty (all the cells are empty), but as well as the blocks are falling, the state of the cells may vary. All the painting work is done every time that the board needs to be repainted: when the player moves the falling block or when the falling block drops because the time interval.

当游戏运行时,控件首先绘制其背景(可以是纯色,渐变或图片),然后绘制每个单元格。 每个单元的状态使用不同的结构保存在内存中。 当游戏开始时,棋盘是空的(所有单元都是空的),但是随着方块的下降,单元的状态可能会有所不同。 每次需要重新粉刷木板时,都会完成所有绘画工作:玩家移动下落块或由于时间间隔下落块下落时。

Really, the heavy work in this class is to control each falling block, its position inside the game board and to control the movements and rotations of each block. Now I'm going to tell you what I've done about this. I'm sure that can be other approaches to solve this problem, and even I'm sure many of them can be better than mine.

确实,此类中的繁重工作是控制每个下降块,它在游戏板上的位置以及控制每个块的运动和旋转。 现在,我将告诉您我对此做了什么。 我确定这可以是解决此问题的其他方法,甚至我敢肯定其中许多方法可以比我的方法更好。

There are 7 different types of block, as seen on this table:

下表中列出了7种不同类型的块:

block-types.png

As you can see, the biggest block is the red one (although in TetrisBox class you can customize the color of each block type), which is 4 cells wide (or high). So I decided to represent each block inside a 4x4 matrix of cells, as this:

如您所见,最大的块是红色的块(尽管在TetrisBox类中,您可以自定义每种块类型的颜色),它的宽度为4个(或较高)。 因此,我决定在4x4的单元矩阵中表示每个块,如下所示:

4x4.png

Each block type has its own number of possible positions inside the 4x4 matrix. For example, type 1 blocks has 2 possible positions, while type 2 blocks has only 1 position and type 3 blocks has until 4 possible positions. In the initialization code for each block I define the possible positions with an array of list of strings. Each character in a string represents a column in the 4x4 matrix; each 4-characters string inside the list represents a row in the 4x4 matrix, and each list of strings inside the array represents one possible position of the block. You can see it better in this picture:

每种块类型在4x4矩阵中都有自己的可能位置数。 例如,类型1块具有2个可能的位置,而类型2块仅具有1个位置,类型3块具有直到4个可能位置。 在每个块的初始化代码中,我使用字符串列表数组定义可能的位置。 字符串中的每个字符代表4x4矩阵中的一列; 列表内的每个4个字符的字符串表示4x4矩阵中的一行,数组内的每个字符串的列表表示该块的一个可能位置。 您可以在这张图片中更好地看到它:

block-1-initialization.png

So, the user can make 4 differents movements to a falling block:

因此,用户可以对下降块进行4种不同的动作:

  • Move Left: moves the block 1 column to the left.

    向左移动:将块1的列向左移动。
  • Move Right: moves the block 1 column to the right.

    向右移动:将块1的列向右移动。
  • Rotate: changes the block position inside the 4x4 block matrix.

    旋转:更改4x4块矩阵内的块位置。
  • Drop: drops down the block 1 row.

    放下:将块放下1行。

计时器选择 (The Timer selection)

When a game starts, a Timer is started to drop down the falling block 1 row at a certain interval. In the .Net Framework there are at least 3 different Timer classes, as you can see on MSDN. I have chosen the System.Timers.Timer class because its metronome accuracy and because it can raise the Elapsed event on the UI thread instead on its own thread, which is useful to change the UI in response to the Elapsed event.

当游戏开始时,启动计时器以一定间隔将下降的块下降1行。 在.Net Framework中,至少有3种不同的Timer类,如您在MSDN上所见。 我之所以选择System.Timers.Timer类,是因为其节拍器准确,并且因为它可以在UI线程上而不是在其自己的线程上引发Elapsed事件,这对于响应Elapsed事件来更改UI很有用。

难点 (Difficulties)

在我之前提到的游戏机中,某些“屏幕”(或游戏阶段)中,游戏增加了一个或多个困难或障碍。 我在课堂上实现了它们。 第一个障碍是填充一个空单元格并将其转换为固定单元格(始终在基线之上或现有固定单元格之上)。 第二个障碍是在底部添加新行,将游戏板上的所有固定单元格向上移动1行。 新行已完全填充,除了一个空单元格。 您可以设定让分出现的时间间隔(例如,您可以创建一个阶段,在该阶段中,每5个新区块出现一个新的随机像元,而每当玩家完成4行时,在底部出现一个新行)。

如何使用 (How to use)

以下是相关属性,方法和事件的快速参考:

()

Properties

物产

  • BackgroundStyle: gets or sets the control's background style (it can be a solid color, a gradient or a picture). If it's a picture, the picture stretches to the control size and must be loaded into the BackgroundImage property. If you want a solid color, you must use the BackColor property.

    BackgroundStyle :获取或设置控件的背景样式(可以是纯色,渐变或图片)。 如果是图片,则图片会拉伸到控件大小,并且必须加载到BackgroundImage属性中。 如果要纯色,则必须使用BackColor属性。
  • Block1Color to Block7Color: gets or sets the color of each block type.

    Block1ColorBlock7Color :获取或设置每种块类型的颜色。
  • LeftKey, RightKey, RotateKey and DropKey: gets or sets the key used for each movement, so you can customize the keys used for play. This can be very useful if you make a 2-players game: they can play with the same keyboard, each one with its own play keys.

    LeftKeyRightKeyRotateKeyDropKey :获取或设置每次移动所使用的键,因此您可以自定义用于播放的键。 如果您制作2人游戏,这可能非常有用:他们可以使用相同的键盘来玩,每个键盘都有自己的播放键。
  • RamdomBlockColor: gets or sets the color to use when a random block appears (see Difficulties).

    RamdomBlockColor :获取或设置在出现随机块时使用的颜色(请参见难度)。
  • UncompleteRowColor: gets or sets the color to use for cells when an uncomplete row appears (see Difficulties).

    UncompleteRowColor :获取或设置在出现不完整的行时用于单元格的颜色(请参阅难度)。
  • TimerInterval: gets or sets the Timer interval (game speed). The shorter interval, the higher speed.

    TimerInterval :获取或设置计时器间隔(游戏速度)。 间隔越短,速度越高。
  • GradientColor1, GradientColor2 and GradientDirection: gets or sets the properties for the gradient background.

    GradientColor1GradientColor2GradientDirection :获取或设置渐变背景的属性。
  • Rows: gets or sets the number of rows in the game board.

    行数 :获取或设置游戏板上的行数。
  • Columns: gets or sets the number of columns in the game board.

    列数 :获取或设置游戏板上的列数。
  • CellSize: gets or sets the cell size in pixels.

    CellSize :获取或设置像元大小(以像素为单位)。

()

Methods

方法

  • StartGame: starts a new game (the Timer is enabled so the blocks start falling).

    StartGame :开始一个新游戏(启用了计时器,因此方块开始掉落)。
  • StopGame: stops a running game.

    StopGame :停止正在运行的游戏。
  • Pause: pauses a running game.

    暂停 :暂停正在运行的游戏。
  • Resume: resumes a paused game.

    恢复 :恢复暂停的游戏。
  • IsRunning: returns True if a game is running (even if it's paused).

    IsRunning :如果游戏正在运行(即使已暂停),则返回True。
  • IsPaused: returns True if a running game is paused.

    IsPaused :如果正在运行的游戏暂停,则返回True。
  • AddRandomBlock: transforms a free board cell into a fixed cell, always above the baseline or above an existing fixed cell (see Difficulties).

    AddRandomBlock :将空闲板单元转换为固定单元,始终在基线之上或在现有固定单元之上(请参阅
  • AddUncompleteLine: adds an uncomplete new line at the bottom of the game board, moving up 1 row all the fixed cells in the board (see Difficulties).

    AddUncompleteLine :在游戏板的底部添加一条未完成的新行,将板上的所有固定单元格向上移动1行(请参阅
  • FreeRowsFromTop: returns the number of completely free (without fixed cells) rows from the top of the game board until the first row with fixed cells. You can use this value to reward the player (the higher number of free rows, the more points he wins).

    FreeRowsFromTop :返回从游戏板顶部到第一行具有固定单元格的完全空闲(无固定单元格)的行数。 您可以使用此值来奖励玩家(空闲行数越高,赢得的积分越多)。

()

Events

大事记

  • FullRows: fires every time the player drops a block and, doing it, completes from 1 to 4 full rows (he must be rewarded!). In the e argument you can check the NumberOfRows property to see how many lines has been completed at once.

    FullRows :每次玩家放下一个方块并执行一次(从1到4个完整行)时都会触发(必须得到奖励!)。 在e参数中,您可以检查NumberOfRows属性以查看一次完成了多少行。
  • GameOver: fires when a block has reached the top of the game board.

    GameOver :当方块到达游戏板顶部时触发。
  • Starting: fires when a game is about to start (because a call to the StartGame method).

    开始 :在游戏即将开始时触发(因为调用StartGame方法)。
  • NewBlock: fires every time a new block is created and starts to fall. In the e argument you can check the BlockType property (which stores the block type of the block just created) and the NextBlockType property (which stores the block type of the next block that will fall once the just created block has been dropped to its ultimate position).

    NewBlock :每次创建新块并开始下降时都会触发。 在e参数中,您可以检查BlockType属性(用于存储刚创建的块的块类型)和NextBlockType属性(用于存储下一个块的块类型,一旦刚创建的块放到其最终位置,该块将落下位置)。

()

Final notes

最后的笔记

Really, there's no much more to say about the TetrisBox class. I have encapsulated all the code in just one class because it's easier to manage and port (although internally it defines and uses several private classes). Following you can see the complete code for the class. Note that this is a 1.0.0.0 version and probably will contain bugs, but the main work is done. If you find some bug, or want any unimplemented feature, write a comment in this article and I will try to fix it.

确实,关于TetrisBox类没有更多要说的了。 我将所有代码封装在一个类中,因为它更易于管理和移植(尽管在内部它定义并使用了几个私有类)。 接下来,您可以看到该类的完整代码。 请注意,这是一个1.0.0.0版本,可能包含错误,但是主要工作已经完成。 如果您发现一些错误,或者想要任何未实现的功能,请在本文中写评论,然后我将尝试对其进行修复。

Imports System.Drawing
Imports System.Runtime.InteropServices
Imports System.Windows.Forms

Public Class TetrisBox
    '**********************************************************************************************
    '*** This is my TetrisBox class made for an Experts Exchange article                        ***
    '*** Feel free to modify and use at your convenience!                                       ***
    '*** Visit my profile on Experts Exchange for more articles and solutions:                  ***
    '*** http://www.experts-exchange.com/members/RolandDeschain.html                            ***
    '**********************************************************************************************
    '*** Version 1.0.0.0                                                                        ***
    '*** Released on 08/19/2014                                                                 ***
    '**********************************************************************************************

    'As this class is, basically, a Tetris drawing surface, I thought that it would be logical to inherit it from the PictureBox class
    Inherits System.Windows.Forms.PictureBox

#Region "Public Enumerations"
    'This enum is for the background style. You can choose between 3 background styles:
    Public Enum BackgroundStyles
        SolidColor  'Draw the background as a solid, unique color (use BackColor property)
        Gradient    'Draw the background as a gradient (use GradientColor1, GradientColor2 and GradientDirection properties)
        Picture     'Draw the background as a picture (use the BackgroundImage property, it will be stretched to the control size)
    End Enum
#End Region

#Region "Private Classes"
    Private Class Block
        'This class represents a falling block in Tetris game. There are 7 different blocks, as you Tetris gamers sure know.
        Private _type As Integer    'Stores the block type. In the Experts Exchange article you'll find a guide matching _type value with each associated block type.
        Public Property Color As Color     'Each block type has a distinctive color.
        Private _rotationsNumber As Integer     'Each block type has a concrete number of possible positions (rotations) in board.
        Private _rotations As Dictionary(Of Integer, List(Of String)) = New Dictionary(Of Integer, List(Of String)) 'Stores the block definition for each rotation inside a 4x4 matrix
        Private _currentRotation As Integer = 1 'The current position of the block
        Public Property X As Integer = 0    'The current column inside board
        Public Property Y As Integer = 0    'The current row inside board

        Public Sub OffsetRotation()
            'Sets the block to the next possible rotation, in response to the user pressing the rotate key
            _currentRotation += 1
            If (_currentRotation > _rotationsNumber) Then _currentRotation = 1
        End Sub

        Public Function FilledCell(ByVal x As Integer, ByVal y As Integer) As Boolean
            'Returns True if the specified x,y position is filled inside the 4x4 matrix
            Return Me.CurrentMatrix()(y).Substring(x, 1).Equals("1")
        End Function

        Public ReadOnly Property CurrentMatrix As List(Of String)
            Get
                'Returns the 4x4 matrix corresponding to the current rotation of the block
                Return _rotations(_currentRotation)
            End Get
        End Property

        Public ReadOnly Property NextRotationMatrix As List(Of String)
            Get
                'Returns the 4x4 matrix corresponding to the next rotation of the block
                Dim nextRotation As Integer = _currentRotation + 1
                If (nextRotation > _rotationsNumber) Then nextRotation = 1
                Return _rotations(nextRotation)
            End Get
        End Property

        Public ReadOnly Property Type As Integer
            Get
                'Returns the block's type
                Return _type
            End Get
        End Property

        Public Sub New(ByVal blockType As Integer)
            'Initializes the class. Depending on the block type it will initialize with its own values
            _type = blockType
            Select Case _type
                Case 1
                    Call InitializeBlock(New List(Of String) From {"0100", "0100", "0100", "0100"}, New List(Of String) From {"0000", "1111", "0000", "0000"})
                Case 2
                    Call InitializeBlock(New List(Of String) From {"0110", "0110", "0000", "0000"})
                Case 3
                    Call InitializeBlock(New List(Of String) From {"0100", "1110", "0000", "0000"}, New List(Of String) From {"0100", "0110", "0100", "0000"}, New List(Of String) From {"1110", "0100", "0000", "0000"}, New List(Of String) From {"0100", "1100", "0100", "0000"})
                Case 4
                    Call InitializeBlock(New List(Of String) From {"0010", "0110", "0100", "0000"}, New List(Of String) From {"0110", "0011", "0000", "0000"})
                Case 5
                    Call InitializeBlock(New List(Of String) From {"0100", "0110", "0010", "0000"}, New List(Of String) From {"0011", "0110", "0000", "0000"})
                Case 6
                    Call InitializeBlock(New List(Of String) From {"0100", "0100", "0110", "0000"}, New List(Of String) From {"0111", "0100", "0000", "0000"}, New List(Of String) From {"0110", "0010", "0010", "0000"}, New List(Of String) From {"0001", "0111", "0000", "0000"})
                Case 7
                    Call InitializeBlock(New List(Of String) From {"0010", "0010", "0110", "0000"}, New List(Of String) From {"0100", "0111", "0000", "0000"}, New List(Of String) From {"0110", "0100", "0100", "0000"}, New List(Of String) From {"0111", "0001", "0000", "0000"})
            End Select
        End Sub

        Private Sub InitializeBlock(ByVal ParamArray rotations() As List(Of String))
            'Initializes a new block

            'Set the number of possible rotations
            _rotationsNumber = rotations.Length
            'Set the 4x4 matrix of each possible rotation
            For k As Integer = 0 To rotations.Length - 1
                _rotations.Add(k + 1, rotations(k))
            Next
        End Sub
    End Class

    Private Class Cell
        'This class represents a single cell (defined by its row and column) inside the game board
        Public Property Row As Integer = 0          'Defines the cell row
        Public Property Column As Integer = 0       'Defines the cell column
        Public Property Fixed As Boolean = False    'When a block drops in the board to its ultimate position (and therefore it cannot be moved anymore), the board cells corresponding to the block filled cells become Fixed
        Public Property Color As Color              'The color in which this cell must be painted in the board

        Public Sub New(ByVal row As Integer, ByVal column As Integer)
            'Instantiates a new Cell object passing its row and column
            Me.Row = row
            Me.Column = column
        End Sub
    End Class

    Private Class CellPoint
        'Really this class is very close to the System.Drawing.Point class, but I've overwritten its ToString function to return the row and column into a string
        'that is helpful to me because it's the Key in the cells collection of the board.
        Public Property Row As Integer = 0          'Represents the row
        Public Property Column As Integer = 0       'Represents the column

        Public Sub New(ByVal row As Integer, ByVal column As Integer)
            'Instantiates a new CellPoint object passing its row and column
            Me.Row = row
            Me.Column = column
        End Sub

        Public Overrides Function ToString() As String
            'Returns a string that can be used as a Key in the cells collection of the board
            Return Me.Row.ToString + "," + Me.Column.ToString
        End Function
    End Class

    Private Class KeyboardHook
        'This class is to globally capture keystrokes.
        'Credits to sim0n for this: http://sim0n.wordpress.com/2009/03/28/vbnet-keyboard-hook-class/
        'although I've modified this slightly to implement the IDisposable interface
        Implements IDisposable

        <DllImport("User32.dll", CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall)> _
        Private Overloads Shared Function SetWindowsHookEx(ByVal idHook As Integer, ByVal HookProc As HookProc, ByVal hInstance As IntPtr, ByVal wParam As Integer) As Integer
        End Function
        <DllImport("User32.dll", CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall)> _
        Private Overloads Shared Function CallNextHookEx(ByVal idHook As Integer, ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Integer
        End Function
        <DllImport("User32.dll", CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall)> _
        Private Overloads Shared Function UnhookWindowsHookEx(ByVal idHook As Integer) As Boolean
        End Function

        <StructLayout(LayoutKind.Sequential)> _
        Private Structure KBDLLHOOKSTRUCT
            Public vkCode As UInt32
            Public scanCode As UInt32
            Public flags As KBDLLHOOKSTRUCTFlags
            Public time As UInt32
            Public dwExtraInfo As UIntPtr
        End Structure

        <Flags()> _
        Private Enum KBDLLHOOKSTRUCTFlags As UInt32
            LLKHF_EXTENDED = &H1
            LLKHF_INJECTED = &H10
            LLKHF_ALTDOWN = &H20
            LLKHF_UP = &H80
        End Enum

        Public Event KeyDown(ByVal Key As Keys)
        Public Event KeyUp(ByVal Key As Keys)

        Private Const WH_KEYBOARD_LL As Integer = 13
        Private Const HC_ACTION As Integer = 0
        Private Const WM_KEYDOWN = &H100
        Private Const WM_KEYUP = &H101
        Private Const WM_SYSKEYDOWN = &H104
        Private Const WM_SYSKEYUP = &H105

        Private Delegate Function HookProc(ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Integer

        Private hookDelegate As HookProc = New HookProc(AddressOf KeyboardProc)
        Private hookID As IntPtr = IntPtr.Zero

        Private Function KeyboardProc(ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Integer
            If (nCode = HC_ACTION) Then
                Dim struct As KBDLLHOOKSTRUCT
                Select Case wParam
                    Case WM_KEYDOWN, WM_SYSKEYDOWN
                        RaiseEvent KeyDown(CType(CType(Marshal.PtrToStructure(lParam, struct.GetType()), KBDLLHOOKSTRUCT).vkCode, Keys))
                    Case WM_KEYUP, WM_SYSKEYUP
                        RaiseEvent KeyUp(CType(CType(Marshal.PtrToStructure(lParam, struct.GetType()), KBDLLHOOKSTRUCT).vkCode, Keys))
                End Select
            End If
            Return CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam)
        End Function

        Public Sub New()
            hookID = SetWindowsHookEx(WH_KEYBOARD_LL, hookDelegate, IntPtr.Zero, 0)
            If hookID = IntPtr.Zero Then
                Throw New Exception("Could not set keyboard hook")
            End If
        End Sub

#Region "IDisposable Support"
        Private disposedValue As Boolean

        ' IDisposable
        Protected Overridable Sub Dispose(disposing As Boolean)
            If Not Me.disposedValue Then
                If disposing Then
                End If

                If Not hookID = IntPtr.Zero Then
                    UnhookWindowsHookEx(hookID)
                End If

            End If
            Me.disposedValue = True
        End Sub

        Protected Overrides Sub Finalize()
            Dispose(False)
            MyBase.Finalize()
        End Sub

        Public Sub Dispose() Implements IDisposable.Dispose
            Dispose(True)
            GC.SuppressFinalize(Me)
        End Sub
#End Region

    End Class

    Private Class Board
        'This class represents the game board. Many of the game logic is implemented here.
        Public Property Rows As Integer = 0                     'Defines the number of rows
        Public Property Columns As Integer = 0                  'Defines the number of columns
        Public Property Cells As Dictionary(Of String, Cell)    'Single-cell collection
        Public Property FallingBlock As Block = Nothing         'The current falling block, if there's any
        Public Property Block1Color As Color = Color.Red        'Allows to customize the color of the type 1 blocks
        Public Property Block2Color As Color = Color.Blue       'Allows to customize the color of the type 2 blocks
        Public Property Block3Color As Color = Color.Green      'Allows to customize the color of the type 3 blocks
        Public Property Block4Color As Color = Color.Aqua       'Allows to customize the color of the type 4 blocks
        Public Property Block5Color As Color = Color.Brown      'Allows to customize the color of the type 5 blocks
        Public Property Block6Color As Color = Color.Yellow     'Allows to customize the color of the type 6 blocks
        Public Property Block7Color As Color = Color.Purple     'Allows to customize the color of the type 7 blocks

        Private _nextBlock As Integer = 0                       'The next block that will fall after the current that is falling

        Public Event FullRows(sender As Object, e As FullRowsEventArgs)     'Fires when the user completes any number of full rows. The completed rows will disappear.
        Public Event GameOver(sender As Object, e As System.EventArgs)      'Fires when a block reaches the top of the game board.
        Public Event GotNewBlock(sender As Object, e As NewBlockEventArgs)  'Fires every time a new block is created. This is useful for adding difficulties every certain number of blocks.

        Public Sub New(ByVal rows As Integer, ByVal columns As Integer)
            'Instantiates a new Board class passing its rows and columns number

            'Set the number of rows
            Me.Rows = rows
            'Set the number of columns
            Me.Columns = columns
            'Initialize the Cell collection
            Me.Cells = New Dictionary(Of String, Cell)
            For row As Integer = 0 To Me.Rows - 1
                For column As Integer = 0 To Me.Columns - 1
                    Me.Cells.Add(row.ToString + "," + column.ToString, New Cell(row, column))
                Next
            Next
        End Sub

        Private Function GetRandomNumber(ByVal lowerbound As Integer, ByVal upperbound As Integer) As Integer
            'To get an integer random number inside a known interval, as seen on MSDN
            Return CInt(Math.Floor((upperbound - lowerbound + 1) * Rnd())) + lowerbound
        End Function

        Public Function Rotate() As Boolean
            'Tries to rotate the current falling block, returning True if the rotation has been done
            If CanRotate() Then
                Me.FallingBlock.OffsetRotation()
                Return True
            Else
                Return False
            End If
        End Function

        Private Function CanRotate() As Boolean
            'Ensures that the current falling block can rotate. It can rotate if, once rotated, keeps inside board margins and doesn't overlap with existing fixed cells.
            If Me.FallingBlock IsNot Nothing Then
                'Get the 4x4 matrix corresponding to the next rotation of the block
                Dim nextRotation As List(Of String) = Me.FallingBlock.NextRotationMatrix
                'Check each filled cell of the block. It must be inside board margins and not overlap with fixed cells.
                For row As Integer = 0 To 3
                    For column As Integer = 0 To 3
                        If nextRotation(row).Substring(column, 1).Equals("1") Then
                            'This is a filled cell.
                            'Translate its coordinates to board-coordinates.
                            Dim pt As CellPoint = BlockToBoard(New CellPoint(row, column))
                            'Check if the cell is inside board margins and doesn't overlap with fixed cells.
                            If (pt.Column < 0) OrElse (pt.Column >= Me.Columns) OrElse (pt.Row < 0) OrElse (pt.Row >= Me.Rows) OrElse Me.Cells(New CellPoint(pt.Row, pt.Column).ToString).Fixed Then
                                Return False
                            End If
                        End If
                    Next
                Next
                Return True
            Else
                Return False
            End If
        End Function

        Public Function MoveLeft() As Boolean
            'Tries to move the current block 1 column to the left, returning True if it has been moved
            If CanMoveLeft() Then
                Me.FallingBlock.X -= 1
                Return True
            Else
                Return False
            End If
        End Function

        Private Function CanMoveLeft() As Boolean
            'Ensures that the current falling block can move to the left. It can move if, once moved, keeps inside board margins and doesn't overlap with existing fixed cells.
            If Me.FallingBlock IsNot Nothing Then
                'Check each filled cell of the block. It must be inside board margins and not overlap with fixed cells.
                For row As Integer = 0 To 3
                    For column As Integer = 0 To 3
                        If Me.FallingBlock.FilledCell(column, row) Then
                            'This is a filled cell.
                            'Translate its coordinates to board-coordinates.
                            Dim pt As CellPoint = BlockToBoard(New CellPoint(row, column))
                            'Check if the cell is inside board margins and doesn't overlap with fixed cells.
                            If pt.Column.Equals(0) OrElse Me.Cells(New CellPoint(pt.Row, pt.Column - 1).ToString).Fixed Then
                                Return False
                            End If
                        End If
                    Next
                Next
                Return True
            Else
                Return False
            End If
        End Function

        Private Function CanMoveRight() As Boolean
            'Ensures that the current falling block can move to the right. It can move if, once moved, keeps inside board margins and doesn't overlap with existing fixed cells.
            If Me.FallingBlock IsNot Nothing Then
                'Check each filled cell of the block. It must be inside board margins and not overlap with fixed cells.
                For row As Integer = 0 To 3
                    For column As Integer = 3 To 0 Step -1
                        If Me.FallingBlock.FilledCell(column, row) Then
                            'This is a filled cell.
                            'Translate its coordinates to board-coordinates.
                            Dim pt As CellPoint = BlockToBoard(New CellPoint(row, column))
                            'Check if the cell is inside board margins and doesn't overlap with fixed cells.
                            If pt.Column.Equals(Me.Columns - 1) OrElse Me.Cells(New CellPoint(pt.Row, pt.Column + 1).ToString).Fixed Then
                                Return False
                            End If
                        End If
                    Next
                Next
                Return True
            Else
                Return False
            End If
        End Function

        Public Function MoveRight() As Boolean
            'Tries to move the current block 1 column to the right, returning True if it has been moved
            If CanMoveRight() Then
                Me.FallingBlock.X += 1
                Return True
            Else
                Return False
            End If
        End Function

        Public Sub NewBlock()
            'Creates a new block.

            If _nextBlock.Equals(0) Then
                'This is the first block requested, so get its block type randomly
                Me.FallingBlock = New Block(GetRandomNumber(1, 7))
            Else
                'This is not the first block, so use the _nextBlock variable
                Me.FallingBlock = New Block(_nextBlock)
            End If
            'Set the block color
            Select Case Me.FallingBlock.Type
                Case 1
                    Me.FallingBlock.Color = Me.Block1Color
                Case 2
                    Me.FallingBlock.Color = Me.Block2Color
                Case 3
                    Me.FallingBlock.Color = Me.Block3Color
                Case 4
                    Me.FallingBlock.Color = Me.Block4Color
                Case 5
                    Me.FallingBlock.Color = Me.Block5Color
                Case 6
                    Me.FallingBlock.Color = Me.Block6Color
                Case 7
                    Me.FallingBlock.Color = Me.Block7Color
            End Select
            'Select the next block to fall (after the one just chosen)
            _nextBlock = GetRandomNumber(1, 7)
            'Position the new block inside the board
            Me.FallingBlock.X = (Me.Columns - 4) / 2
            Me.FallingBlock.Y = 0
            'Notify user that a new block has been created
            RaiseEvent GotNewBlock(Me, New NewBlockEventArgs(Me.FallingBlock.Type, _nextBlock))
        End Sub

        Public Sub CheckBlock()
            'Checks game logic with the falling block. Concretely, checks that:
            '1) The falling block has reached the top of the board (it overlaps with existing fixed cells)
            '2) The falling block has reached its ultimate position and, therefore, it cannot be moved anymore (for example, it has been dropped to the bottom of the board)

            If Me.FallingBlock IsNot Nothing Then
                'Let's see if the falling block overlaps with existing fixed cells. If so, the game is over
                Dim overlapBlock As Boolean = False
                'Check each existing filled cell on the current falling block
                For row As Integer = 0 To 3
                    For column As Integer = 0 To 3
                        If Me.FallingBlock.FilledCell(column, row) Then
                            'This is a filled cell
                            'Translate its coordinates to board-coordinates
                            Dim pt As CellPoint = BlockToBoard(New CellPoint(row, column))
                            If Me.Cells(pt.ToString).Fixed Then
                                overlapBlock = True
                                Exit For
                            End If
                        End If
                    Next
                    If overlapBlock Then Exit For
                Next
                If overlapBlock Then
                    'An overlap exists. This is game over!
                    RaiseEvent GameOver(Me, New System.EventArgs)
                Else
                    'Now check if the current falling block has been dropped to its ultimate position. If so, transform each filled cell of the block into a fixed cell on the board.
                    Dim fixBlock As Boolean = False

                    For column As Integer = 0 To 3
                        For row As Integer = 3 To 0 Step -1
                            If Me.FallingBlock.FilledCell(column, row) Then
                                Dim pt As CellPoint = BlockToBoard(New CellPoint(row, column))
                                'If the falling block has a filled cell just over the board's bottom, or just over a fixed cell in the next line, it must be fixed
                                If pt.Row.Equals(Me.Rows - 1) OrElse Me.Cells(New CellPoint(pt.Row + 1, pt.Column).ToString).Fixed Then
                                    fixBlock = True
                                End If
                                Exit For
                            End If
                        Next
                        If fixBlock Then Exit For
                    Next

                    If fixBlock Then
                        'Transform each filled cell on the block into a fixed cell on the board
                        For row As Integer = 0 To 3
                            For column As Integer = 0 To 3
                                If Me.FallingBlock.FilledCell(column, row) Then
                                    Dim pt As CellPoint = BlockToBoard(New CellPoint(row, column))
                                    Me.Cells(pt.ToString).Fixed = True
                                    Me.Cells(pt.ToString).Color = Me.FallingBlock.Color
                                End If
                            Next
                        Next
                        Me.FallingBlock = Nothing
                        'As a block has been dropped and fixed, check if the player has completed full rows
                        Call CheckFullRows()
                    End If
                End If
            End If
        End Sub

        Private Sub CheckFullRows()
            'Checks if the player has completed full rows (this is the main Tetris objective!)

            Dim fullRows As List(Of Integer) = New List(Of Integer)

            'Check each row from bottom to top
            For row As Integer = Me.Rows - 1 To 0 Step -1
                Dim fullRow As Boolean = True
                'Check if all columns are fixed in the row
                For column As Integer = 0 To Me.Columns - 1
                    If (Not Me.Cells(row.ToString + "," + column.ToString).Fixed) Then
                        fullRow = False
                        Exit For
                    End If
                Next
                'The row is full filled
                If fullRow Then fullRows.Add(row)
            Next
            If fullRows.Count > 0 Then
                'Delete the full rows
                For Each row As Integer In fullRows
                    Call DeleteRow(row)
                Next
                'Notify the user (you should probably reward the player)
                RaiseEvent FullRows(Me, New FullRowsEventArgs(fullRows.Count))
            End If
        End Sub

        Private Sub DeleteRow(ByVal row As Integer)
            'Delete a row

            'To delete a row, drop-down the entire board over the deleted row and clear the first row
            For r As Integer = row To 1 Step -1
                For col As Integer = 0 To Me.Columns - 1
                    Me.Cells(r.ToString + "," + col.ToString).Fixed = Me.Cells((r - 1).ToString + "," + col.ToString).Fixed
                    Me.Cells(r.ToString + "," + col.ToString).Color = Me.Cells((r - 1).ToString + "," + col.ToString).Color
                Next
            Next
            'Clear the first row
            For col As Integer = 0 To Me.Columns - 1
                Me.Cells("0," + col.ToString).Fixed = False
            Next
        End Sub

        Private Function BlockToBoard(ByVal p As CellPoint) As CellPoint
            'Translate a block coordinate into a board coordinate
            Return New CellPoint(p.Row + Me.FallingBlock.Y, p.Column + Me.FallingBlock.X)
        End Function

        Private Function BoardToBlock(ByVal p As CellPoint) As CellPoint
            'Translate a board coordinate into a block coordinate
            Return New CellPoint(p.Row - Me.FallingBlock.Y, p.Column - Me.FallingBlock.X)
        End Function

        Public Function GetCellColor(ByVal p As CellPoint) As Color
            'Returns the color in which a cell must be painted. Defaults to Transparent, which means that the cell is empty.
            Dim output As Color = Color.Transparent

            If Me.Cells(p.Row.ToString + "," + p.Column.ToString).Fixed Then
                'If the cell if fixed in the board, return its fixed color
                output = Me.Cells(p.Row.ToString + "," + p.Column.ToString).Color
            Else
                'If the cell is inside the 4x4 matrix of the falling block...
                If (Me.FallingBlock IsNot Nothing) AndAlso CellIsInsideBlock(p.Row, p.Column) Then
                    'Translate to block coordinates
                    Dim pt As CellPoint = BoardToBlock(p)
                    'If 
                    If Me.FallingBlock.FilledCell(pt.Column, pt.Row) Then
                        output = Me.FallingBlock.Color
                    End If
                    'If Me.FallingBlock.CurrentMatrix(pt.Row).Substring(pt.Column, 1).Equals("1") Then
                    'End If
                End If
            End If

            Return output
        End Function

        Private Function CellIsInsideBlock(ByVal row As Integer, ByVal column As Integer) As Boolean
            'Returns true if the row,column coordinate (board system) is inside the current falling block 4x4 matrix
            Return (row >= Me.FallingBlock.Y) AndAlso (row <= (Me.FallingBlock.Y + 3)) AndAlso (column >= Me.FallingBlock.X) AndAlso (column <= (Me.FallingBlock.X + 3))
        End Function
    End Class
#End Region

#Region "Public Classes"
    Public Class FullRowsEventArgs
        'Argument for the FullRows event.
        Inherits System.EventArgs

        'Contains the number of rows filled at once, from 1 to 4. In the classic Tetris game, the more rows you filled at once, the more points are you rewarded with.
        Public Property NumberOfRows As Integer = 0

        Public Sub New(ByVal numberOfRows As Integer)
            Me.NumberOfRows = numberOfRows
        End Sub
    End Class

    Public Class NewBlockEventArgs
        'Argument for the NewBlock event.
        Inherits System.EventArgs

        'This property notifies about the falling block type
        Public Property BlockType As Integer = 0
        'This property notifies about the next falling block type
        Public Property NextBlockType As Integer = 0

        Public Sub New(ByVal blockType As Integer, ByVal nextBlockType As Integer)
            Me.BlockType = blockType
            Me.NextBlockType = nextBlockType
        End Sub
    End Class
#End Region

#Region "Public Events"
    'Fires when the player completes entire rows
    Public Event FullRows(sender As Object, e As FullRowsEventArgs)
    'Fires when a block reaches the top of the board and, therefore, overlaps with existing fixed cells
    Public Event GameOver(sender As Object, e As System.EventArgs)
    'Fires when the game is about to start
    Public Event Starting(sender As Object, e As System.EventArgs)
    'Fires every time a new block is created
    Public Event NewBlock(sender As Object, e As NewBlockEventArgs)
#End Region

#Region "Private Variables"
    Private _rows As Integer = 20       'Number of board rows. Internal storage for Rows property.
    Private _columns As Integer = 10    'Number of board columns. Internal storage for Columns property.
    Private _cellSize As Integer = 25   'Cell size (in pixels). Internal storage for CellSize property. [Board width = Columns x CellSize] [Board height = Rows x CellSize]
    Private _backgroundStyle As BackgroundStyles = BackgroundStyles.SolidColor  'Background style. Internal storage for BackgroundStyle property.
    Private _gradientColor1 As Color = Color.SteelBlue  'First gradient color. Internal storage for GradientColor1 property.
    Private _gradientColor2 As Color = Color.Black      'Second gradient color. Internal storage for GradientColor2 property.
    Private _gradientDirection As Drawing2D.LinearGradientMode = Drawing2D.LinearGradientMode.Vertical  'Gradient direction. Internal storage for GradientDirection property.
    Private _timer As System.Timers.Timer = Nothing     'Timer used to drop the falling block 1 row automatically.
    Private _board As Board                             'The board.
    Private _running As Boolean = False                 'Stores a value indicating whether the game is running or not
    Private _pause As Boolean = False                   'Stores a value indicating whether the game is paused or not
    Private WithEvents _hook As KeyboardHook = Nothing  'Hook to catch keystrokes
#End Region

#Region "Constructor"
    Public Sub New()
        'Set DoubleBuffered to true to avoid screen-flickering.
        Me.DoubleBuffered = True
        'Instantiate the Timer and set a default interval of 1000 ms.
        _timer = New Timers.Timer(1000)
        'Set the SynchronizingObject of the timer (if not, the Elapsed event fires into its own subprocess and it's impossible to update the UI)
        _timer.SynchronizingObject = Me
        'Add a handler to the Elapsed event
        AddHandler _timer.Elapsed, AddressOf TimerElapsed
    End Sub
#End Region

#Region "Public Auto-Implemented Properties"
    Public Property RandomBlockColor As Color = Color.LightYellow       'The color of random-generated blocks (difficulty)
    Public Property UncompleteRowColor As Color = Color.LightYellow     'The color of umcomplete rows (difficulty)
    Public Property LeftKey As Keys = Keys.Left     'Allows to customize the Left key for the game (defaults to the left direction key)
    Public Property RightKey As Keys = Keys.Right   'Allows to customize the Right key for the game (defaults to the right direction key)
    Public Property RotateKey As Keys = Keys.Up     'Allows to customize the Rotate key for the game (defaults to the up direction key)
    Public Property DropKey As Keys = Keys.Down     'Allows to customize the Drop key for the game (defaults to the down direction key)
    Public Property Block1Color As Color = Color.Red        'Allows to customize the color of the type 1 blocks
    Public Property Block2Color As Color = Color.Blue       'Allows to customize the color of the type 2 blocks
    Public Property Block3Color As Color = Color.Green      'Allows to customize the color of the type 3 blocks
    Public Property Block4Color As Color = Color.Aqua       'Allows to customize the color of the type 4 blocks
    Public Property Block5Color As Color = Color.Brown      'Allows to customize the color of the type 5 blocks
    Public Property Block6Color As Color = Color.Yellow     'Allows to customize the color of the type 6 blocks
    Public Property Block7Color As Color = Color.Purple     'Allows to customize the color of the type 7 blocks
#End Region

#Region "Public Properties"
    'Gets or sets the Timer interval (game speed). The shorter interval, the higher speed.
    Public Property TimerInterval As Integer
        Get
            Return _timer.Interval
        End Get
        Set(value As Integer)
            _timer.Interval = value
        End Set
    End Property

    'Gets or sets the first color of the gradient when BackgroundStyle = Gradient
    Public Property GradientColor1 As Color
        Get
            Return _gradientColor1
        End Get
        Set(value As Color)
            _gradientColor1 = value
            If Me.BackgroundStyle = BackgroundStyles.Gradient Then Me.Invalidate()
        End Set
    End Property

    'Gets or sets the second color of the gradient when BackgroundStyle = Gradient
    Public Property GradientColor2 As Color
        Get
            Return _gradientColor2
        End Get
        Set(value As Color)
            _gradientColor2 = value
            If Me.BackgroundStyle = BackgroundStyles.Gradient Then Me.Invalidate()
        End Set
    End Property

    'Gets or sets the direction of the gradient when BackgroundStyle = Gradient
    Public Property GradientDirection As Drawing2D.LinearGradientMode
        Get
            Return _gradientDirection
        End Get
        Set(value As Drawing2D.LinearGradientMode)
            _gradientDirection = value
            If Me.BackgroundStyle = BackgroundStyles.Gradient Then Me.Invalidate()
        End Set
    End Property

    'Gets or sets the background style
    Public Property BackgroundStyle As BackgroundStyles
        Get
            Return _backgroundStyle
        End Get
        Set(value As BackgroundStyles)
            _backgroundStyle = value
            Me.Invalidate()
        End Set
    End Property

    'Gets or sets the number of rows in the game board
    Public Property Rows As Integer
        Get
            Return _rows
        End Get
        Set(value As Integer)
            _rows = value
            Me.Invalidate()
        End Set
    End Property

    'Gets or sets the number of columns in the game board
    Public Property Columns As Integer
        Get
            Return _columns
        End Get
        Set(value As Integer)
            _columns = value
            Me.Invalidate()
        End Set
    End Property

    'Gets or sets the cell size (in pixels)
    Public Property CellSize As Integer
        Get
            Return _cellSize
        End Get
        Set(value As Integer)
            _cellSize = value
            Me.Invalidate()
        End Set
    End Property
#End Region

#Region "OnPaint"
    Protected Overrides Sub OnPaint(e As PaintEventArgs)
        'First, set the control's size
        Me.Width = (Me.Columns * Me.CellSize) - (Me.Columns - 1)
        Me.Height = (Me.Rows * Me.CellSize) - (Me.Rows - 1)

        'Draw the background
        Select Case Me.BackgroundStyle
            Case BackgroundStyles.SolidColor
                'Draws a single color
                e.Graphics.Clear(Me.BackColor)

            Case BackgroundStyles.Gradient
                'Draws a gradient
                Using b As Drawing2D.LinearGradientBrush = New Drawing2D.LinearGradientBrush(Me.DisplayRectangle, Me.GradientColor1, Me.GradientColor2, Me.GradientDirection)
                    e.Graphics.FillRectangle(b, Me.DisplayRectangle)
                End Using

            Case BackgroundStyles.Picture
                'Draws a picture if BackgroundImage is set; if not, draws a single color
                If Me.BackgroundImage IsNot Nothing Then
                    e.Graphics.DrawImage(Me.BackgroundImage, 0, 0, Me.Width, Me.Height)
                Else
                    e.Graphics.Clear(Me.BackColor)
                End If
        End Select

        'Paint cells
        If _board IsNot Nothing Then
            For row As Integer = 0 To Me.Rows - 1
                For column As Integer = 0 To Me.Columns - 1
                    'Get the cell color
                    Dim c As Color = _board.GetCellColor(New CellPoint(row, column))
                    If c <> Color.Transparent Then
                        'Draw the cell background
                        Using b As SolidBrush = New SolidBrush(c)
                            e.Graphics.FillRectangle(b, New Rectangle(column * (Me.CellSize - 1), row * (Me.CellSize - 1), Me.CellSize - 1, Me.CellSize - 1))
                        End Using
                        'Draw the cell border
                        e.Graphics.DrawRectangle(Pens.Black, New Rectangle(column * (Me.CellSize - 1), row * (Me.CellSize - 1), Me.CellSize - 1, Me.CellSize - 1))
                    End If
                Next
            Next
        End If
    End Sub
#End Region

#Region "Public Methods And Functions"
    Public Sub StartGame()
        'Starts a game
        If (Not Me.IsRunning) AndAlso (Not Me.IsPaused) Then
            'Initialize the ramdom number generator
            Randomize()
            'Initialize the game board
            _board = New Board(Me.Rows, Me.Columns)
            With _board
                .Block1Color = Me.Block1Color
                .Block2Color = Me.Block2Color
                .Block3Color = Me.Block3Color
                .Block4Color = Me.Block4Color
                .Block5Color = Me.Block5Color
                .Block6Color = Me.Block6Color
                .Block7Color = Me.Block7Color
            End With
            'Add handlers to catch board events
            AddHandler _board.FullRows, AddressOf CatchFullRows
            AddHandler _board.GameOver, AddressOf CatchGameOver
            AddHandler _board.GotNewBlock, AddressOf CatchNewBlock
            'Set that the game is running
            _running = True
            'Initialize the keyboard hook
            _hook = New KeyboardHook()
            'Notify user
            RaiseEvent Starting(Me, New System.EventArgs)
            'Start the timer
            _timer.Start()
        End If
    End Sub

    Public Sub StopGame()
        'Stops a running game
        If Me.IsRunning() Then
            'Stop the timer
            _timer.Stop()
            'Not running
            _running = False
            'Not paused
            _pause = False
            'Dispose the keyboard hook
            _hook.Dispose()
            _hook = Nothing
        End If
    End Sub

    Public Function FreeRowsFromTop() As Integer
        'In classic Tetris game, the game was not continuous, but the player should be playing among different screens, each one with its one goal. For example, the first
        'screen was an initial empty board and the player must play until he completes 5 full rows. In this game system, when a new screen was achieved, the game rewarded
        'the player not only for the full rows completed, but also for the completely free rows from the top of the board until the first not-completely free row. So you
        'can use this function to get the number of free rows from board's top.
        Dim freeRows As Integer = 0
        If _board IsNot Nothing Then
            For row As Integer = 0 To Me.Rows - 1
                Dim freeRow As Boolean = True
                For column As Integer = 0 To Me.Columns - 1
                    If _board.Cells(row.ToString + "," + column.ToString).Fixed Then
                        freeRow = False
                        Exit For
                    End If
                Next
                If freeRow Then
                    freeRows += 1
                Else
                    Exit For
                End If
            Next
        End If
        Return freeRows
    End Function

    Public Sub AddRandomBlock()
        'Adds a random block to the board. The block is always positioned over the board's bottom or a existing fixed cell.

        'Choose the column
        Dim whichColumn As Integer
        Do
            whichColumn = GetRandomNumber(0, Me.Columns - 1)
        Loop Until (Not _board.Cells("0," + whichColumn.ToString).Fixed)

        'Check for a fixed cell in the column to position the random block over it.
        For row As Integer = Me.Rows - 1 To 0 Step -1
            If (Not _board.Cells(row.ToString + "," + whichColumn.ToString).Fixed) Then
                _board.Cells(row.ToString + "," + whichColumn.ToString).Fixed = True
                _board.Cells(row.ToString + "," + whichColumn.ToString).Color = Me.RandomBlockColor
                Me.Invalidate()
                Exit For
            End If
        Next
    End Sub

    Public Sub AddUncompleteRow()
        'Adds an uncomplete row at the bottom of the board, moving up the rest of the cells. This can cause a game over.
        Dim forceGameOver As Boolean = False

        'If there's a fixed cell in first row, moving up the entire board will cause a game over
        If ThereIsSomethingInFirstRow() Then
            forceGameOver = True
        End If
        'Move all the board up 1 row
        For row As Integer = 0 To Me.Rows - 2
            For column As Integer = 0 To Me.Columns - 1
                _board.Cells(row.ToString + "," + column.ToString).Fixed = _board.Cells((row + 1).ToString + "," + column.ToString).Fixed
                _board.Cells(row.ToString + "," + column.ToString).Color = _board.Cells((row + 1).ToString + "," + column.ToString).Color
            Next
        Next
        'Choose the empty column
        Dim emptyColumn As Integer = GetRandomNumber(0, Me.Columns - 1)
        'Draw the uncomplete row
        For column As Integer = 0 To Me.Columns - 1
            If column.Equals(emptyColumn) Then
                _board.Cells((Me.Rows - 1).ToString + "," + column.ToString).Fixed = False
            Else
                _board.Cells((Me.Rows - 1).ToString + "," + column.ToString).Fixed = True
                _board.Cells((Me.Rows - 1).ToString + "," + column.ToString).Color = Me.UncompleteRowColor
            End If
        Next
        'Redraw
        Me.Invalidate()
    End Sub

    Public Function IsRunning() As Boolean
        'Returns True if a game is running
        Return _running
    End Function

    Public Function IsPaused() As Boolean
        'Returns True if a game is paused
        Return _pause
    End Function

    Public Sub Pause()
        'Pauses a running game
        If Me.IsRunning AndAlso (Not Me.IsPaused) Then
            _pause = True
        End If
    End Sub

    Public Sub [Resume]()
        If Me.IsPaused Then
            _pause = False
        End If
    End Sub
#End Region

#Region "Private Methods And Functions"
    'Timer elapsed event
    Private Sub TimerElapsed(sender As Object, e As System.EventArgs)
        If (Not Me.IsPaused) Then
            'Redraw the board and check the current falling block
            Call RedrawAndCheckBlock()

            If _board.FallingBlock Is Nothing Then
                'If there's not a falling block, create new one
                Call _board.NewBlock()
                Call RedrawAndCheckBlock()
            Else
                'Drop the falling block 1 line
                _board.FallingBlock.Y += 1
            End If
        End If
    End Sub

    'Redraws the board and checks the falling block
    Private Sub RedrawAndCheckBlock()
        Me.Invalidate()
        Call _board.CheckBlock()
    End Sub

    'Catches keystrokes
    Private Sub _hook_KeyDown(Key As Keys) Handles _hook.KeyDown
        If (Not Me.IsPaused) Then
            If Key = Me.LeftKey Then
                If _board.MoveLeft() Then Call RedrawAndCheckBlock()
            ElseIf Key = Me.RightKey Then
                If _board.MoveRight() Then Call RedrawAndCheckBlock()
            ElseIf Key = Me.DropKey Then
                'If the user presses the Drop key, force a 1-line drop
                Call TimerElapsed(Nothing, Nothing)
            ElseIf Key = Me.RotateKey Then
                If _board.Rotate() Then Call RedrawAndCheckBlock()
            End If
        End If
    End Sub

    'The board notifies that the player has completed full rows. Notify and redraw board.
    Private Sub CatchFullRows(sender As Object, e As FullRowsEventArgs)
        RaiseEvent FullRows(Me, e)
        Me.Invalidate()
    End Sub

    'The board notifies that the game is over.
    Private Sub CatchGameOver(sender As Object, e As System.EventArgs)
        'Stop the game
        Call StopGame()

        'Notify the user
        RaiseEvent GameOver(Me, e)
    End Sub

    'The board notifies that a new block has been created. Notify.
    Private Sub CatchNewBlock(sender As Object, e As NewBlockEventArgs)
        RaiseEvent NewBlock(sender, e)
    End Sub

    'Checks if there is a fixed cell in the first row
    Private Function ThereIsSomethingInFirstRow() As Boolean
        Dim output As Boolean = False
        For column = 0 To Me.Columns - 1
            If _board.Cells("0," + column.ToString).Fixed Then
                output = True
                Exit For
            End If
        Next
        Return output
    End Function

    Private Function GetRandomNumber(ByVal lowerbound As Integer, ByVal upperbound As Integer) As Integer
        'Get a random integer number as seen on MSDN
        Return CInt(Math.Floor((upperbound - lowerbound + 1) * Rnd())) + lowerbound
    End Function
#End Region
End Class

As you will see, the code is well commented. Feel free to download, modify, use and write your own Tetris games. If you improve this class any way, please let me know! I'm always happy to learn. I've tested 2 instances running in parallel (for a 2-player game) and it works well too. I also attach a .zip file containing a Visual Studio 2013 VB.net Winforms project with a simple game example.

如您所见,该代码已被很好地注释。 随意下载,修改,使用和编写自己的Tetris游戏。 如果您以任何方式提高了这门课,请告诉我! 我总是很高兴学习。 我已经测试了2个并行运行的实例(对于2人游戏),它也运行良好。 我还附加了一个.zip文件,其中包含带有简单游戏示例的Visual Studio 2013 VB.net Winforms项目。

TetrisBoxTest.zip TetrisBoxTest.zip

Finally, I hope you find this code fun and useful. If so, please vote as helpful.

最后,希望您发现此代码有趣且有用。 如果是这样,请投票为有帮助。

翻译自: https://www.experts-exchange.com/articles/15159/Having-fun-with-graphics-Part-III-Let's-play-Tetris.html

c语言俄罗斯方块方块图形

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值