Mine Scout
Programmatically create Labels forming a VB.Net Label Array
Uses a Recursive Procedure
Uses a Recursive Procedure
This game is a Minesweeper clone. It demonstrates creating Labels programmatically and manipulating them via their Controls Index number. It also has a recursive procedure that calls itself to cut down the amount of code required.
Start by designing the form. Its size is 646, 518. The 2 objects on the form have the index numbers 0 and 1. The number increases for new controls when adding them at run time.
This is different to adding them at design time where the last control added is zero and all the other controls move one down, or maybe that's up.
Start by designing the form. Its size is 646, 518. The 2 objects on the form have the index numbers 0 and 1. The number increases for new controls when adding them at run time.
This is different to adding them at design time where the last control added is zero and all the other controls move one down, or maybe that's up.
Double click the form. Start with the 2 at the moment empty procedures
Public Class frmMines
'Every object on the form has a Control Index number
'This project manipulates Labels using their index numbers
'The first tile index will be 2 as there are already 2 controls on the form (0 and 1)
Dim intTotalMines As Integer
Dim intNotMines As Integer 'Keep track of mines marked incorrectly
Dim intNumbers = New Integer() {-32, -31, -30, -1, 1, 30, 31, 32}
'The array above holds the 8 index number differences of the 8 Labels around a Tile Label
'This is used when counting mines around a tile,
'and also when revealing tiles around a blank tile
'Change the outer 3 numbers if the grid width of 31 is changed
'Create 2 temporarily empty procedures so the Form Load procedure doesn't show an error
Sub myLabel_click(sender As Object, e As System.Windows.Forms.MouseEventArgs)
'This handles all the tile labels on the grid per the AddHandler in the Form_Load procedure
'More to come
End Sub
Sub Setup()
'More to come
End Sub
'Double click the form
Private Sub frmScout_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Me.KeyPreview = True 'Enables shortcut keys
Dim intCol As Integer
Dim intRow As Integer
Dim intSize As Integer = 20 'Height and width of the tiles + distance from left of form
Dim intTop As Integer = 40 'Distance of tiles from the top of the form
'Alternatives for below:
'myLabel.Font = New Font("Arial", 12, FontStyle.Regular)
'myLabel.Height = intSize
'myLabel.Width = intSize
'Create all the tiles in a 21 rows x 31 columns grid
For intRow = 0 To 20
For intCol = 0 To 30
Dim myLabel As New Label
myLabel.Font = New Font(myLabel.Font, FontStyle.Bold)
myLabel.TextAlign = ContentAlignment.MiddleCenter
myLabel.Left = intSize + intCol * intSize - intCol
myLabel.Top = intTop + intRow * intSize - intRow
myLabel.Size = New Size(intSize, intSize)
myLabel.BorderStyle = BorderStyle.FixedSingle
'Hide the tiles around the outside as they are not part of the minefield
'This simplifies the counting mines and Reveal procedures
If intRow = 0 Or intRow = 20 Or intCol = 0 Or intCol = 30 Then
myLabel.Visible = False
End If
Me.Controls.Add(myLabel)
'Point to the click procedure above for all labels just created
AddHandler myLabel.Click, AddressOf Me.myLabel_click
Next intCol
Next intRow
Setup()
End Sub
End Class
We can now run the project.
Public Class frmMines
'Every object on the form has a Control Index number
'This project manipulates Labels using their index numbers
'The first tile index will be 2 as there are already 2 controls on the form (0 and 1)
Dim intTotalMines As Integer
Dim intNotMines As Integer 'Keep track of mines marked incorrectly
Dim intNumbers = New Integer() {-32, -31, -30, -1, 1, 30, 31, 32}
'The array above holds the 8 index number differences of the 8 Labels around a Tile Label
'This is used when counting mines around a tile,
'and also when revealing tiles around a blank tile
'Change the outer 3 numbers if the grid width of 31 is changed
'Create 2 temporarily empty procedures so the Form Load procedure doesn't show an error
Sub myLabel_click(sender As Object, e As System.Windows.Forms.MouseEventArgs)
'This handles all the tile labels on the grid per the AddHandler in the Form_Load procedure
'More to come
End Sub
Sub Setup()
'More to come
End Sub
'Double click the form
Private Sub frmScout_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Me.KeyPreview = True 'Enables shortcut keys
Dim intCol As Integer
Dim intRow As Integer
Dim intSize As Integer = 20 'Height and width of the tiles + distance from left of form
Dim intTop As Integer = 40 'Distance of tiles from the top of the form
'Alternatives for below:
'myLabel.Font = New Font("Arial", 12, FontStyle.Regular)
'myLabel.Height = intSize
'myLabel.Width = intSize
'Create all the tiles in a 21 rows x 31 columns grid
For intRow = 0 To 20
For intCol = 0 To 30
Dim myLabel As New Label
myLabel.Font = New Font(myLabel.Font, FontStyle.Bold)
myLabel.TextAlign = ContentAlignment.MiddleCenter
myLabel.Left = intSize + intCol * intSize - intCol
myLabel.Top = intTop + intRow * intSize - intRow
myLabel.Size = New Size(intSize, intSize)
myLabel.BorderStyle = BorderStyle.FixedSingle
'Hide the tiles around the outside as they are not part of the minefield
'This simplifies the counting mines and Reveal procedures
If intRow = 0 Or intRow = 20 Or intCol = 0 Or intCol = 30 Then
myLabel.Visible = False
End If
Me.Controls.Add(myLabel)
'Point to the click procedure above for all labels just created
AddHandler myLabel.Click, AddressOf Me.myLabel_click
Next intCol
Next intRow
Setup()
End Sub
End Class
We can now run the project.
Complete the code for the Setup procedure
Sub Setup()
Dim intCol As Integer
Dim intRow As Integer
Dim strInput As String
Dim intMines As Integer
Do
Do
strInput = InputBox("Enter mines to find from 50 to 150", "Set difficulty", "100")
Loop Until IsNumeric(strInput)
intTotalMines = CInt(strInput)
Loop Until intTotalMines > 49 And intTotalMines < 151
'Reset all tiles. No need to do the top and bottom non-visible tiles.
For i As Integer = 34 To 620
Me.Controls.Item(i).Text = ""
Me.Controls.Item(i).BackColor = SystemColors.ControlLight
Me.Controls.Item(i).ForeColor = SystemColors.ControlLight
Next
'Place mines randomly but not in the outer non-visible tiles
Randomize()
Do
intCol = Int(29 * Rnd() + 1)
intRow = Int(19 * Rnd())
If Me.Controls.Item(intCol + 33 + 31 * intRow).Text <> "M" Then
Me.Controls.Item(intCol + 33 + 31 * intRow).Text = "M"
intMines += 1
End If
Loop Until intMines = intTotalMines
intNotMines = 0
End Sub
Nothing will show if this is run. Here is an option to display the mines.
Double click the New Game button. Switch to the MouseDown event.
Private Sub btnNewGame_MouseDown(sender As Object, e As MouseEventArgs) Handles btnNewGame.MouseDown
If e.Button = MouseButtons.Left Then
Setup()
End If
'Here are more options which is why the MouseDown event had to be used
'1. Reveal all the mines with Right click
If e.Button = MouseButtons.Right Then
For i As Integer = 34 To 620
If Me.Controls.Item(i).Text = "M" Then
Me.Controls.Item(i).ForeColor = Color.Black
End If
Next
End If
'2. Show the instructions/splash screen if you have one
'If e.Button = MouseButtons.Middle Then
'frmSplash.Show()
'End If
End Sub
Complete the code to click a label. It calls the recursive Reveal procedure. It means the Reveal procedure calls itself.
Sub myLabel_click(sender As Object, e As System.Windows.Forms.MouseEventArgs)
'This handles all the labels on the grid per the AddHandler in the Form_Load procedure
If Me.Controls.Item(Controls.IndexOf(sender)).BackColor = Color.Red Then
Exit Sub 'Don't allow to step on a mine again
End If
If e.Button = MouseButtons.Right Then
If Me.Controls.Item(Controls.IndexOf(sender)).BackColor = Color.Yellow Then
'Unmark a location
Me.Controls.Item(Controls.IndexOf(sender)).BackColor = SystemColors.ControlLight
Me.Controls.Item(Controls.IndexOf(sender)).ForeColor = SystemColors.ControlLight
'Check whether it's a mine
If Me.Controls.Item(Controls.IndexOf(sender)).Text = "M" Then
intTotalMines += 1
Else
intNotMines -= 1
End If
Else
'Mark a location
If Me.Controls.Item(Controls.IndexOf(sender)).BackColor = SystemColors.ControlLight Then
Me.Controls.Item(Controls.IndexOf(sender)).BackColor = Color.Yellow
'Hide whatever the tile says
Me.Controls.Item(Controls.IndexOf(sender)).ForeColor = Color.Yellow
'Check whether it's a mine
If Me.Controls.Item(Controls.IndexOf(sender)).Text = "M" Then
intTotalMines -= 1
Else
intNotMines += 1
End If
End If
End If
ElseIf e.Button = MouseButtons.Left Then 'Reveal a tile
'Maybe it's a mine!
If Me.Controls.Item(Controls.IndexOf(sender)).Text = "M" Then
Me.Controls.Item(Controls.IndexOf(sender)).BackColor = Color.Red
MsgBox("Oh, no, you have trodden on a mine!", MsgBoxStyle.Critical, "Found a mine!")
intTotalMines -= 1
Else
'Reveal the tile plus the tiles around it if it's blank
'If any around it are blank then Reveal will keep revealing
Reveal(Controls.IndexOf(sender))
End If
End If
If intNotMines = 0 And intTotalMines = 0 Then
MsgBox("All mines found!", MsgBoxStyle.Exclamation, "End of this game!")
End If
End Sub
That leaves the Reveal procedure to come.
Sub myLabel_click(sender As Object, e As System.Windows.Forms.MouseEventArgs)
'This handles all the labels on the grid per the AddHandler in the Form_Load procedure
If Me.Controls.Item(Controls.IndexOf(sender)).BackColor = Color.Red Then
Exit Sub 'Don't allow to step on a mine again
End If
If e.Button = MouseButtons.Right Then
If Me.Controls.Item(Controls.IndexOf(sender)).BackColor = Color.Yellow Then
'Unmark a location
Me.Controls.Item(Controls.IndexOf(sender)).BackColor = SystemColors.ControlLight
Me.Controls.Item(Controls.IndexOf(sender)).ForeColor = SystemColors.ControlLight
'Check whether it's a mine
If Me.Controls.Item(Controls.IndexOf(sender)).Text = "M" Then
intTotalMines += 1
Else
intNotMines -= 1
End If
Else
'Mark a location
If Me.Controls.Item(Controls.IndexOf(sender)).BackColor = SystemColors.ControlLight Then
Me.Controls.Item(Controls.IndexOf(sender)).BackColor = Color.Yellow
'Hide whatever the tile says
Me.Controls.Item(Controls.IndexOf(sender)).ForeColor = Color.Yellow
'Check whether it's a mine
If Me.Controls.Item(Controls.IndexOf(sender)).Text = "M" Then
intTotalMines -= 1
Else
intNotMines += 1
End If
End If
End If
ElseIf e.Button = MouseButtons.Left Then 'Reveal a tile
'Maybe it's a mine!
If Me.Controls.Item(Controls.IndexOf(sender)).Text = "M" Then
Me.Controls.Item(Controls.IndexOf(sender)).BackColor = Color.Red
MsgBox("Oh, no, you have trodden on a mine!", MsgBoxStyle.Critical, "Found a mine!")
intTotalMines -= 1
Else
'Reveal the tile plus the tiles around it if it's blank
'If any around it are blank then Reveal will keep revealing
Reveal(Controls.IndexOf(sender))
End If
End If
If intNotMines = 0 And intTotalMines = 0 Then
MsgBox("All mines found!", MsgBoxStyle.Exclamation, "End of this game!")
End If
End Sub
That leaves the Reveal procedure to come.
Sub Reveal(intIndex As Integer)
Dim intTiles As Integer
Dim intMines As Integer
'This procedure calls itself 8 times if this tile is blank until all tiles around it are revealed
'If another blank tile is there then it continues with that tile etc
'But only if it's un-revealed at the moment and not an outer non-visible tile
If Me.Controls.Item(intIndex).BackColor <> SystemColors.ControlLight _
Or Me.Controls.Item(intIndex).Visible = False Then Exit Sub
For intTiles = 0 To 7
'Calculate how many mines are around this tile
'The hidden tiles are also cycled through when at the outer tiles
'This reduces the complexity of this procedure
'A number from the intNumbers() array adds the appropriate index numbers around a tile
If Me.Controls.Item(intIndex + intNumbers(intTiles)).Text = "M" Then
intMines += 1
End If
Next
'Record the number of mines in the tile itself if there are any
If intMines > 0 Then
Me.Controls.Item(intIndex).Text = intMines.ToString
End If
Me.Controls.Item(intIndex).ForeColor = Color.Black
Me.Controls.Item(intIndex).BackColor = Color.White
'So, is it blank, ie no mines around it?
If intMines = 0 Then
'Here is the recursive call to reveal the 8 tiles around this blank tile
'The intNumbers() array again adds the appropriate index number offsets around a tile
For i As Integer = 0 To 7
Reveal(intIndex + intNumbers(i))
Next
End If
End Sub
Dim intTiles As Integer
Dim intMines As Integer
'This procedure calls itself 8 times if this tile is blank until all tiles around it are revealed
'If another blank tile is there then it continues with that tile etc
'But only if it's un-revealed at the moment and not an outer non-visible tile
If Me.Controls.Item(intIndex).BackColor <> SystemColors.ControlLight _
Or Me.Controls.Item(intIndex).Visible = False Then Exit Sub
For intTiles = 0 To 7
'Calculate how many mines are around this tile
'The hidden tiles are also cycled through when at the outer tiles
'This reduces the complexity of this procedure
'A number from the intNumbers() array adds the appropriate index numbers around a tile
If Me.Controls.Item(intIndex + intNumbers(intTiles)).Text = "M" Then
intMines += 1
End If
Next
'Record the number of mines in the tile itself if there are any
If intMines > 0 Then
Me.Controls.Item(intIndex).Text = intMines.ToString
End If
Me.Controls.Item(intIndex).ForeColor = Color.Black
Me.Controls.Item(intIndex).BackColor = Color.White
'So, is it blank, ie no mines around it?
If intMines = 0 Then
'Here is the recursive call to reveal the 8 tiles around this blank tile
'The intNumbers() array again adds the appropriate index number offsets around a tile
For i As Integer = 0 To 7
Reveal(intIndex + intNumbers(i))
Next
End If
End Sub
I've just started a game, revealed and and flagged a few mines.