vb@rchiv
VB Classic
VB.NET
ADO.NET
VBA
C#
TOP-Angebot: 17 bzw. 24 Entwickler-Vollversionen zum unschlagbaren Preis!  
 vb@rchiv Quick-Search: Suche startenErweiterte Suche starten   RSS-Feeds  | Newsletter  | Impressum  | Datenschutz  | vb@rchiv CD Vol.6  | Shop Copyright ©2000-2017
 
zurück
Rubrik: OOP / Tools   |   VB-Versionen: VB5, VB601.03.02
MineSweeper in VB programmieren

Fast jeder wird es kennen, das Windows-Spiel, bei dem man auf Minensuche geht. Die Rede ist von 'Minesweeper', ein vom Prinzip her recht einfach zu spielendes Spiel. Aber wie lässt sich solch ein Spiel in Visual Basic programmieren? Die Antwort erfahren Sie hier in unserem Workshop.

Autor:  MartoengBewertung:     [ Jetzt bewerten ]Views:  11.535 

Neue Version! sevEingabe 3.0 (für VB6 und VBA)
Das Eingabe-Control der Superlative! Noch besser und noch leistungsfähiger!
Jetzt zum Einführungspreis       - Aktionspreis nur für kurze Zeit gültig -

Fast jeder wird es kennen, das Windows-Spiel, bei dem man auf Minensuche geht. Die Rede ist von 'Minesweeper', ein vom Prinzip her recht einfach zu spielendes Spiel. Aber wie lässt sich solch ein Spiel in Visual Basic programmieren? Die Antwort erfahren Sie hier in unserem Workshop.

Einleitung

Wer kennt nicht dieses kleine Spiel was einem einfach keine Ruhe gibt bis man es geschafft hat und alle Minen gefunden hat. Dieser Workshop soll Ihnen helfen, Ihren eigenen Minensucher zu programmieren.

Um das Spiel selbst zu programmieren, müssen wir natürlich zunächst selbst einmal alle Regeln und Äußerlichkeiten (Abb. 1) festhalten.

Abb.1: Die MineSweeper-Oberfläche
Abb.1: Die MineSweeper-Oberfläche

Um zunächst bei den Äußerlichkeiten zu bleiben:

  1. Das Spielfeld ist unterteilt in einzelne Felder
  2. Das Spielfeld hat eine Mindestbreite von 9 x 9 Feldern
  3. Es wird die Gesamt-Minenzahl und die Zeit (in Sekunden) angezeigt
  4. Es gibt eine "Neues Spiel"-Befehlsschaltfläche

Jetzt gehen wir einmal auf die Regeln ein, die wirklich ganz einfach sind. Es gibt ein Spielfeld, auf dem eine bestimmte Anzahl Minen verteilt wird. Die maximale Anzahl wird folgendermaßen berechnet: (Breite - 1) * (Höhe - 1). Diese Formel benötigen wir noch später, wenn wir ein benutzerdefiniertes Spielfeld erlauben.

Jetzt geht es an die Minenverteilung. Die Minen können überall auf dem Spielfeld angeordnet werden, dabei ruhig zwei oder mehr nebeneinander. Um diese Minen herum kommen aber Zahlen. Die Zahl auf diesem Feld wird von der Anzahl der Minen um dieses Feld bestimmt. Ein Beispiel (M = Mine):

111
1M1
111

Sollten jedoch zwei Minen irgendwie nebeneinander liegen, verändert sich das Bild natürlich:

1221
1MM1
1221

Die Zahl auf dem Feld sagt also aus, wie viele Minen um das Feld herumliegen. Das sind die wichtigsten Dinge, die restlichen Regeln erkläre ich noch beim Programmieren. Um nicht zu theoretisch zu bleiben fangen wir jetzt auch gleich mal an.

Erstellen der Oberfläche

Wir erstellen zunächst die Oberfläche. Dazu brauchen wir erst einmal ein Formular und wir benötigen die Ressourcen (die Digital-Zahlen und die einzelnen Bilder für die Felder).

Wir erstellen ein neues Standard-EXE-Projekt und benennen Form1 in frmMain um. Wichtig ist vor allem, dass die ScaleMode-Eigenschaft auf 3 (Pixel) gesetzt wird!

Hier die Ressourcen:

Die Feld-Symbole
Abb. 2: Feld-Symbole
Die Feld-Symbole
Abb. 3: Digital-Zahlen

Diese Bilder laden wir in Bildfelder (PictureBox: picMine und picTime). Jetzt müssen folgende Eigenschaften für die Bildfelder gesetzt werden:

  • AutoRedraw = True (das Bild wird im Arbeitsspeicher abgelegt)
  • Appearance = 0 (keine 3D-Darstellung)
  • BorderStyle = 0 (kein Rand)
  • Visible = False (darf nicht auf dem Formular sichtbar sein)
  • AutoSize = True (kann auch manuell gesetzt werden)

Nun platzieren wir ein drittes Bildfeld namens picPlay in die Mitte des Formulars. Für dieses setzen wir die gleichen Eigenschaften wie für picMine und picTime, bis auf Visible, denn wir wollen ja sehen was wir spielen
picPlay ist unser eigentliches Spielfeld. Auf ihm werden später die Felder gezeichnet. Um schon einmal die Oberfläche für unsere spätere Bedürfnisse anzupassen, fügen wir noch zwei kleinere Bildfelder ein, die folgende Eigenschaften besitzen:

  • AutoRedraw = True
  • Appearance = 0
  • BorderStyle = 0
  • BackColor = 0 (Schwarz)
  • Height = 25
  • Width = 41

Und in die Mitte oben kommt noch unsere Befehlsschaltfläche (CommandButton) cmdNew hinzu. Sie bekommt das Bild eines hübschen kleinen Smileys (im Verzeichnis "Common\Graphics\Icons\Misc\", Datei "FACE02.ICO"). Dabei nicht vergessen, dass eine Befehlsschaltfläche die Eigenschaft Style = 1 braucht, um das Bild anzuzeigen.

Für die spätere Zeitnahme ist auch noch ein Timer nötig. Dieser hat folgende Eigenschaften:

  • Name = tmrTime
  • Enabled = False
  • Interval = 1000 (entspricht 1 Sekunde)

Ist das alles erledigt sollte das Ganze so aussehen:

Entwurfsansicht der Oberfläche in der IDE
Abb. 4: Entwurfsansicht der Oberfläche in der VB-IDE

API-Funktionen: Das Modul mDeclarations

Nun geht es ans Eingemachte, den CODE!
Zunächst brauchen wir ein Modul mDeclarations. In dieses Modul kommen alle Deklarationen, genauer gesagt alle API-Funktions-Deklarationen:

Option Explicit
 
' Win32-API-Deklarationen
Public Declare Function BitBlt Lib "gdi32" ( _
  ByVal hDestDC As Long, _
  ByVal X As Long, _
  ByVal Y As Long, _
  ByVal nWidth As Long, _
  ByVal nHeight As Long, _
  ByVal hSrcDC As Long, _
  ByVal xSrc As Long, _
  ByVal ySrc As Long, _
  ByVal dwRop As Long) As Long
 
Public Declare Function PlaySound Lib "winmm.dll" _
  Alias "PlaySoundA" ( _
  ByVal lpszName As String, _
  ByVal hModule As Long, _
  ByVal dwFlags As Long) As Long
 
' Sound-Konstanten
Public Const SND_RESOURCE = &H40004
Public Const SND_ASYNC = &H1
Public Const SND_MEMORY = &H4
Public Const SND_NODEFAULT = &H2

Die Funktion BitBlt brauchen wir für das Übertragen der Bilddaten aus einem Bildfeld in ein anderes. PlaySound wird benötigt, wenn wir hinterher Sound-Effekte hinzufügen.

Jetzt noch ein paar Enumerationen und Typendeklarationen, die wir später brauchen.

' Eigene Enumerationen
Public Enum GRAPHIC_CONSTANTS
  gc_Default = 0  ' normales Feld
  gc_Free = 1     ' bereits geklicktes aber freies Feld
  gc_Flag = 2     ' Feld mit Fahne
  gc_Question = 3 ' Feld mit Fragezeichen
  gc_Mine = 4     ' Mine
  gc_RedMine = 5  ' Mine mit rotem Hintergrund
  gc_One = 6      ' Feld mit "1"
  gc_Two = 7      ' Feld mit "2"
  gc_Three = 8    ' Feld mit "3"
  gc_Four = 9     ' Feld mit "4"
  gc_Five = 10    ' Feld mit "5"
  gc_Six = 11     ' Feld mit "6"
  gc_Seven = 12   ' Feld mit "7"
  gc_Eight = 13   ' Feld mit "8"
End Enum
 
Public Enum VALUE_CONSTANTS
  vc_Free = 1     ' Freies Feld
  vc_Mine = 4     ' Mine
  vc_RedMine = 5  ' Mine mit rotem Hintergrund
  vc_One = 6      ' Feld mit "1"
  vc_Two = 7      ' Feld mit "2"
  vc_Three = 8    ' Feld mit "3"
  vc_Four = 9     ' Feld mit "4"
  vc_Five = 10    ' Feld mit "5"
  vc_Six = 11     ' Feld mit "6"
  vc_Seven = 12   ' Feld mit "7"
  vc_Eight = 13   ' Feld mit "8"
End Enum
 
' Eigene Typendeklaration
Public Type FIELD
  ' Welche Grafik wird für das Feld angezeigt
  Graphic As GRAPHIC_CONSTANTS
 
  ' Welchen (verborgenen) Wert hat das Feld
  Value As VALUE_CONSTANTS
 
  ' Wurde schon geklickt?
  Clicked As Boolean
End Type

Funktionen für den Spielablauf

Für den Spielablauf brauchen wir aber noch ein paar globale Variablen. Hierzu fügen wir unserem Projekt ein weiteres Modul hinzu und nennen es mPlay

Option Explicit
 
' Wieviele Kästchen breit?
Public Fields_X As Integer
 
' Wieviele Kästchen hoch?
Public Fields_Y As Integer
 
' Wieviele Minen gibt es?
Public MineCount As Integer
 
' Wieviele Flaggen wurden gesetzt?
Public FlagCount As Integer
 
' Wieviele Kästchen wurden bereits geklickt?
Public Clicked As Integer
 
' Wieviele Sekunden wurde schon gespielt?
Public Seconds As Integer
 
' Dialog "Benutzerdefinierter" - abgebrochen?
Public UserCancel As Boolean
 
' Array der Felder
Public m_Fields() As FIELD

Als Erstes erstellen wir nun die Prozedur zum Initialisieren des Spielfeldes. Hier werden noch keine Werte für einzelne Felder gesetzt, wohl aber die Grenzen des Spielfeldes und die Anzahl der Minen. Die nachfolgenden Prozeduren erstellen wir ebenfalls im Modul mPlay.

' Spielfeld initialisieren (noch keine Werte setzen)
Public Sub InitArea(Hor_Fields As Integer, _
  Ver_Fields As Integer, Mines As Integer)
 
  ' Breite und Höhe
  Fields_X = Hor_Fields
  Fields_Y = Ver_Fields
 
  ' Neu dimensionieren
  ReDimArray Hor_Fields * Ver_Fields
 
  ' Breite des Bildfeldes
  frmMain.picPlay.Width = Hor_Fields * 16
  frmMain.picPlay.Height = Ver_Fields * 16
 
  ' Minen
  MineCount = Mines
End Sub
' Array neu dimensionieren
Public Sub ReDimArray(NewLength As Integer)
  ReDim Preserve m_Fields(0 To (NewLength - 1))
End Sub

Wie man sieht, wird also ein Array von 0 bis Anzahl der Felder - 1 dimensioniert. Jetzt fügen wir zunächst ein paar Routinen zur Navigation im Array hinzu.

' Reihe eines Feldes ermitteln
Public Function GetRow(Index As Integer) As Integer
  GetRow = Int(Index / Fields_Y)
End Function
' Spalte eines Feldes ermitteln
Public Function GetCol(Index As Integer) As Integer
  GetCol = Index - (GetRow(Index) * Fields_Y)
End Function

Dies sind die beiden wichtigsten Funktionen. Sie geben Zeile/Reihe (Row) und Spalte (Col) des durch Index gegebenen Feldes an.

Nun brauchen wir noch Funktionen, um die Nachbar-Felder zu ermitteln (also oben links, oben, oben rechts, links, rechts, unten links, unten, unten rechts).

' Nachbarfeld (links)
Public Function GetLeftField(Index As Integer) _
  As Integer
 
  Dim fld As Integer
 
  fld = Index - 1
  GetLeftField = IIf(GetRow(fld) < GetRow(Index), -1, fld)
End Function
 
' Nachbarfeld (rechts)
Public Function GetRightField(Index As Integer) _
  As Integer
 
  Dim fld As Integer
 
  fld = Index + 1
  GetRightField = IIf(GetRow(fld) <> GetRow(Index) Or _
    fld >= Fields_X * Fields_Y Or _
    GetCol(fld) <> GetCol(Index) + 1, -1, fld)
End Function
 
' Nachbarfeld (oben)
Public Function GetTopField(Index As Integer) _
  As Integer
 
  Dim fld As Integer
 
  fld = Index - Fields_X
  GetTopField = IIf(fld < 0, -1, fld)
End Function
 
' Nachbarfeld (unten)
Public Function GetBottomField(Index As Integer) _
  As Integer
 
  Dim fld As Integer
 
  fld = Index + Fields_X
  GetBottomField = IIf(fld > Fields_X * Fields_Y - 1 Or _
    GetCol(fld) <> GetCol(Index), -1, fld)
End Function
 
' Nachbarfeld (oben links)
Public Function GetTopLeftField(Index As Integer) _
  As Integer
 
  Dim fld As Integer
 
  fld = Index - Fields_X - 1
  GetTopLeftField = IIf(fld < 0 Or _
    GetRow(fld) <= GetRow(Index) - 2, -1, fld)
End Function
 
' Nachbarfeld (oben rechts)
Public Function GetTopRightField(Index As Integer) _
  As Integer
 
  Dim fld As Integer
 
  fld = Index - Fields_X + 1
  GetTopRightField = IIf((GetCol(fld) <> (GetCol(Index) + 1)) Or _
    (fld < 0), -1, fld)
End Function
 
' Nachbarfeld (unten rechts)
Public Function GetBottomRightField(Index As Integer) _
  As Integer
 
  Dim fld As Integer
 
  fld = Index + Fields_X + 1
  GetBottomRightField = IIf(fld > Fields_X * Fields_Y - 1 Or _
    GetCol(Index) <> GetCol(fld) - 1, -1, fld)
End Function
 
' Nachbarfeld (unten links)
Public Function GetBottomLeftField(Index As Integer) _
  As Integer
 
  Dim fld As Integer
 
  fld = Index + Fields_X - 1
  GetBottomLeftField = IIf(fld > Fields_X * Fields_Y - 1 Or _
    GetCol(Index) <> GetCol(fld) + 1, -1, fld)
End Function

Das sind jetzt erst einmal ziemlich viele Funktionen, mit denen man so ohne den Kontext nichts anfangen kann. Keine Angst, das Verständnis kommt schon noch. Zu beachten ist aber, dass diese Funktionen alle den Wert -1 zurückgeben, falls das gesuchte Feld nicht existiert (bspw. das gesuchte Feld über dem Feld ganz links oben kann nicht existieren).

Spielfeld und Felder zeichnen

Nun formulieren wir eine Prozedur, die ein einzelnes Feld zeichnet und eine, die alle Felder zeichnet.

Hierzu fügen wir unserem Projekt wiederum ein neues Modul hinzu und nennen es mDraw.

Option Explicit
 
' Ein Feld zeichnen
Public Sub DrawField(Index As Integer, _
  Optional ByVal ReDraw As Boolean = True)
 
  BitBlt frmMain.picPlay.hDC, GetCol(Index) * 16, _
    GetRow(Index) * 16, 16, 16, frmMain.picMine.hDC, _
    0, m_Fields(Index).Graphic * 16, vbSrcCopy
  If ReDraw Then frmMain.picPlay.Refresh
End Sub

Hier verwenden wir die API-Funktion BitBlt, um die Bilddaten zu übertragen. Wir ermitteln also die Spalte und die Zeile des Feldes. Multipliziert mit 16 ergibt das dann jeweils die x- und y-Position zum Zeichnen in picPlay. Bei der Quelle nutzen wir unseren selbst definierten Datentyp FIELD aus. Er hat eine Variable Graphic, die die Information speichert, welches Bild für dieses Feld gezeichnet werden soll. Wie der Wert dafür zustande gekommen ist, kommt später. Im Moment soll einfach nur blind die Zeichen-Funktion programmiert werden. Wir nutzen die konstante Höhe eines Feldes aus, die genau 16 Pixel beträgt. Multipliziere ich also die Variable Graphic mit 16, so erhalte ich die y-Position zum Blitten aus der Quell-Bitmap. Der Parameter ReDraw legt fest, ob das Bildfeld picPlay neu gezeichnet werden soll, wenn die Operation abgeschlossen ist.

Nun aber zur Funktion um alle Felder zu zeichnen. Da wir genau die Grenzen kennen ist es nur allzu logisch eine For-Schleife zu verwenden.

' Alle Felder zeichnen
Public Sub DrawArea()
  Dim X As Integer
  Dim Y As Integer
  Dim produkt As Integer
  Dim fld As Integer
 
  produkt = Fields_X * Fields_Y
  For Y = 0 To Fields_Y
    For X = 0 To Fields_X
      fld = Y * Fields_X + X
      If fld < produkt Then mDraw.DrawField fld, False
    Next X
  Next Y
  frmMain.picPlay.Refresh
End Sub

Nur um das Zeichnen zu komplettieren nun noch folgende Funktion, die sich wohl von selbst erklärt - wir erneuern die Ansicht des Bildfeldes:

' Bildfeld neu zeichnen
Public Sub ReDraw()
  frmMain.picPlay.Refresh
End Sub

Die FIELD-Struktur

Um jetzt mit den spannenden Routinen zu beginnen, schauen wir uns den Datentyp FIELD noch einmal etwas genauer an.

Public Type FIELD
  Graphic As GRAPHIC_CONSTANTS
  Value As VALUE_CONSTANTS
  Clicked As Boolean
End Type

Die Variable Graphic sagt nur aus, welche Grafik verwendet wird (Freies Feld, Eins, Zwei, Fahne, usw.). Um es einfacher zu machen hat diese Variable den Datentyp GRAPHIC_CONSTANTS. Dies ermöglicht, dass wir nicht umständlich schreiben müssen "Graphic = 5" und uns dabei immer merkenmüssen, welchen Wert 5 eigentlich repräsentiert, sondern wir sagen "Graphic = gc_One", so dass sofort klar ist, dass damit die Eins gemeint ist.

Die Variable Value repräsentiert den eigentlichen Wert, der sich "unter dem Feld verbirgt". Dies kann "Frei", "Mine", "Rote Mine" (also eine Mine auf die man gerade geklickt hat) oder eine Zahl sein.

Die Variable Clicked gibt lediglich an, ob dieses Feld bereits angeklickt wurde, denn dann muss nicht extra noch einmal der ganze Aufruf von Funktionen durchlaufen werden, wie wir ihn gleich sehen werden.

Nun benötigen wir noch ein paar Prozeduren, um diese Eigenschaften vereinfacht zu setzen, so dass sich z.B. beim Klicken eines Feldes automatisch das Bild ändert.

' Wert eines Feldes setzen
Public Sub SetValue(Index As Integer, _
  New_Value As VALUE_CONSTANTS)
 
  m_Fields(Index).Value = New_Value
End Sub
' Status "geklickt" setzen
Public Function SetClicked(Index As Integer, _
  Optional ByVal New_Clicked As Boolean = True) As Boolean
 
  SetClicked = False
  With m_Fields(Index)
    If .Clicked = False Then
      .Clicked = New_Clicked
      If New_Clicked Then _
        mPlay.Clicked = mPlay.Clicked + 1
    Else
      .Clicked = New_Clicked
    End If
 
    If Clicked = Fields_X * Fields_Y - MineCount Then
      ' Gewonnen ;-)
      SetClicked = True
    End If
  End With
End Function
' Grafik ermitteln
Public Function GetGraphic(Index As Integer) _
  As GRAPHIC_CONSTANTS
 
  GetGraphic = m_Fields(Index).Graphic
End Function
' Grafik setzen (für Flag und Fragezeichen)
Public Sub SetGraphic(Index As Integer, _
  NewGraphic As GRAPHIC_CONSTANTS, _
  Optional ByVal ReDraw As Boolean = True)
 
  m_Fields(Index).Graphic = NewGraphic
  DrawField Index, ReDraw
End Sub

Der Parameter ReDraw gibt immer an, ob das Bildfeld picPlay neugezeichnet werden soll oder nicht.

Jetzt kommen wir unserem Ziel immer näher. Es geht nun darum, die Funktion zu schreiben, die ein Feld mit einer Zahl belegt, weil im Nachbarfeld eine Mine ist.

' Wenn keine Mine, um eins erhöhen
' bzw. beim ersten Eintrag auf vc_One setzen
Public Sub IncreaseField(fld As Integer)
  If fld <> -1 Then
    With m_Fields(fld)
      If .Value <> vc_Mine Then
        If .Value >= vc_One Then
          .Value = .Value + 1
        Else
          .Value = vc_One
        End If
      End If
    End With
  End If
End Sub

IncreaseField heißt soviel wie "Feld erhöhen" und macht aus freien Feldern Felder mit einer 1. Bei Feldern, die bereits eine Zahl haben wird diese um 1 erhöht. Der maximale Wert ist, wie wir bereits wissen, 8. Dies geschieht aber nur, wenn dieses Feld keine Mine besitzt (m_Fields(fld).Value <> vc_Mine).

Nun folgt die Prozedur zum Verteilen der Minen. Im Prinzip ist sie ganz einfach, aber trotzdem etwas längerauf Grund der Funktionsaufrufe:

' Minen verteilen
Public Sub InitMines()
  Dim n As Integer
  Dim fld As Integer
 
  For n = 1 To MineCount
    Do
      fld = Int(Fields_X * Fields_Y * Rnd)
    Loop While m_Fields(fld).Value = vc_Mine
    m_Fields(fld).Value = vc_Mine
  Next n
 
  For n = 0 To Fields_X * Fields_Y - 1
    With m_Fields(n)
      If .Value = vc_Mine Then
        ' Nachbarfelder setzen
        IncreaseField GetTopLeftField(n)
        IncreaseField GetTopField(n)
        IncreaseField GetTopRightField(n)
        IncreaseField GetLeftField(n)
        IncreaseField GetRightField(n)
        IncreaseField GetBottomLeftField(n)
        IncreaseField GetBottomField(n)
        IncreaseField GetBottomRightField(n)
      ElseIf .Value = 0 Then
        .Value = vc_Free
      End If
    End With
  Next n
End Sub

Zuerst werden in einer For-Schleife alle Minen verteilt. Es wird solange ein Index durch eine Zufallszahl ermittelt, bis keine Mine gefunden wird, damit nicht zwei Minen auf ein einziges Feld gesetzt werden.

Reagieren auf Mausereignisse

Jetzt fügen wir noch die Prozedur hinzu, die beim Klicken auf das Spielfeld ausgelöst wird. Wen die Details interessieren, der kann sie sich noch genauer anschauen. Es wird lediglich jedes Nachbarfeld überprüft. Hierbei ist aber zu beachten, dass wenn das Ausgangs-Feld frei ist, ein rekursiver Aufruf stattfindet. Dieselbe Prozedur namens ClickField wird mit dem Nachbarfeld-Index aufgerufen. Dies setzt sichso lange fort, bis kein angrenzendes freies Feld mehr abgedeckt ist. Hierbei wird noch eine andere Regel beachtet, nämlich die Fahne und das Fragezeichen. Die Fahne (Wert gc_Flag) wird vom Benutzer mit der rechten Maustaste platziert. Sie bedeutet, dass der Spieler darunter eine Mine versteckt sieht, weshalb dieses Feld auch nicht durch unsere Prozedur aufgedeckt werden darf. Ein Fragezeichen hingegen wird einfach "plattgemacht".

Hier nun die Prozedur:

' Es wird auf ein Feld geklickt
Public Sub ClickField(Index As Integer, _
  Optional ByVal ReDraw As Boolean = True)
 
  Dim fld As Integer
 
  With m_Fields(Index)
    If .Clicked = False Then
      If .Value = vc_Mine Then .Value = vc_RedMine
      .Graphic = m_Fields(Index).Value
      DrawField Index, ReDraw
 
      If SetClicked(Index, True) Then
        Win   ' Gewonnen ;-)
        Exit Sub
      End If
 
      If m_Fields(Index).Value = vc_RedMine Then
        Lose  ' Verloren ;-(
        Exit Sub
      End If        
 
      ' Nachbarfelder
      If .Value = vc_Free Then
        ' Oben links
        fld = GetTopLeftField(Index)
        FieldAction fld
 
        ' Oben
        fld = GetTopField(Index)
        FieldAction fld
 
        ' Oben rechts
        fld = GetTopRightField(Index)
        FieldAction fld
 
        ' Links
        fld = GetLeftField(Index)
        FieldAction fld
 
        ' Rechts
        fld = GetRightField(Index)
        FieldAction fld
 
        ' Unten links
        fld = GetBottomLeftField(Index)
        FieldAction fld
 
        ' Unten
        fld = GetBottomField(Index)
        FieldAction fld
 
        ' Unten rechts
        FieldAction fld
      End If
    End If
  End With
End Sub
' Aktion ausführen (nach Klick)
Private Sub FieldAction(ByVal iFeld As Integer)
  If iFeld <> -1 Then
    With m_Fields(iFeld)
      If Not .Graphic = gc_Flag Then
        If .Value = vc_Free Then
          ClickField iFeld, False
        ElseIf .Value >= vc_One Then
          SetClicked iFeld, True
          .Graphic = .Value
          DrawField iFeld, False
        End If
      End If
    End With
  End If
End Sub

So. Alles geschafft? Das Programm ist natürlich noch nicht fertig. Es fehlen noch ein paar Dinge. So z.B. die Prozedur zum Abfragen der Maus-Ereignisse.

Die Abfrage findet natürlich nicht im Modul, sondern in der Hauptform frmMain statt:

' Die Maus wird gedrückt
Private Sub picPlay_MouseDown(Button As Integer, _
  Shift As Integer, X As Single, Y As Single)
 
  Dim fld As Integer
 
  If tmrTime.Enabled = False Then tmrTime.Enabled = True
  fld = Int(X / 16) + Int(Y / 16) * Fields_X
  If Button = 1 Then
    ' linke Maustaste
    mDraw.ClickField (fld), False
    picPlay.Refresh
  ElseIf Button = 2 Then
    ' rechte Maustaste
    Select Case GetGraphic(fld)
      Case gc_Default
        If FlagCount < MineCount Then
          SetGraphic fld, gc_Flag
          FlagCount = FlagCount + 1
          DrawMineCount
        Else
          SetGraphic fld, gc_Question
        End If
      Case gc_Flag
        SetGraphic fld, gc_Question
        FlagCount = FlagCount - 1
        DrawMineCount
      Case gc_Question
        SetGraphic fld, gc_Default
    End Select
  End If
End Sub

Bei einem Klick mit der linken Maustaste wird das Feld aufgedeckt (ClickField-Prozedur), bei einem Rechtsklick wird zunächst überprüft, ob das Feld bereits eine Fahne / ein Fragezeichen hat oder ob es frei ist. Je nachdem was davon zutrifft, wird dann eine neue Grafik über SetGraphic gesetzt. Ebenfalls hier vermerkt: der Timer. Der Timer darf ja auch erst starten, wenn der Benutzer das 1. Mal auf das Spielfeld klickt.

Beispielprojekt zum Downloaden

Wer den Quellcode herunterlädt, der kann sich noch einmal in Ruhe alle Funktionen (auch die, die hier nicht weiter erwähnt sind) anschauen und nachvollziehen.

Zudem sind in dem Projekt noch Menüs, verschiedene Spielfelder u.v.m. integriert.

VB MineSweeper
Abb. 6: MineSweeper in VB - Leider verloren

Anmerkung:
Sound ist zwar im Menü schon vorgesehen, ist aber noch nicht in einer Resourcen-Datei im Projekt inbegriffen.

Dieser Workshop wurde bereits 11.535 mal aufgerufen.

Über diesen Workshop im Forum diskutieren
Haben Sie Fragen oder Anregungen zu diesem Workshop, können Sie gerne mit anderen darüber in unserem Forum diskutieren.

Neue Diskussion eröffnen

nach obenzurück


Anzeige

Kauftipp Unser Dauerbrenner!Diesen und auch alle anderen Workshops finden Sie auch auf unserer aktuellen vb@rchiv  Vol.6
(einschl. Beispielprojekt!)

Ein absolutes Muss - Geballtes Wissen aus mehr als 8 Jahren vb@rchiv!
- nahezu alle Tipps & Tricks und Workshops mit Beispielprojekten
- Symbol-Galerie mit mehr als 3.200 Icons im modernen Look
Weitere Infos - 4 Entwickler-Vollversionen (u.a. sevFTP für .NET), Online-Update-Funktion u.v.m.
 
   

Druckansicht Druckansicht Copyright ©2000-2017 vb@rchiv Dieter Otter
Alle Rechte vorbehalten.
Microsoft, Windows und Visual Basic sind entweder eingetragene Marken oder Marken der Microsoft Corporation in den USA und/oder anderen Ländern. Weitere auf dieser Homepage aufgeführten Produkt- und Firmennamen können geschützte Marken ihrer jeweiligen Inhaber sein.

Diese Seiten wurden optimiert für eine Bildschirmauflösung von mind. 1280x1024 Pixel