Als weiterführende Übungsaufgaben werden in diesem Kapitel zwei lauffähige Beispielprojekte vorgeführt. Haben Sie den geschilderten Aufbau verstanden, können Sie später eigene Verbesserungen oder Erweiterungen einbringen.
11 Beispielprojekte
Bei den beiden Beispielprojekten handelt es sich zum einen um das bekannte Tetris-Spiel und zum anderen einen Vokabeltrainer.
11.1 Spielprogramm Tetris 

Im Folgenden wird das bekannte Spielprogramm Tetris in einer vereinfachten, nachvollziehbaren Version für Visual Basic realisiert und erläutert. Das Programm beinhaltet:
- ein zweidimensionales Feld
- einen Timer
- einen Zufallsgenerator
- die Erzeugung und Löschung von Steuerelementen zur Laufzeit
- die Zuordnung von Ereignisprozeduren zu Steuerelementen, die erst zur Laufzeit erzeugt werden
Abbildung 11.1 zeigt die Benutzeroberfläche des Programms.
Abbildung 11.1 Tetris
11.1.1 Spielablauf 

Panel fällt herunter
Nach Programmstart fällt ein Steuerelement vom Typ Panel in einer von acht möglichen Farben so weit herunter, bis es auf den Rand des Spielfelds oder ein anderes Panel trifft. Es kann mithilfe der drei Buttons Links (Li), Rechts (Re) und Drop (Dr) bewegt werden. Drop bewirkt ein sofortiges Absenken des Panels auf die unterste mögliche Position.
Level
Falls sich drei gleichfarbige Panels untereinander oder nebeneinander befinden, so verschwinden sie. Panels, die sich eventuell darüber befinden, rutschen nach. Anschließend wird die Fallgeschwindigkeit der Panels erhöht, d. h. die Schwierigkeitsstufe wird gesteigert, man gelangt zum nächsten Level.
Ende
Sobald ein Panel nur noch in der obersten Zeile platziert werden kann, ist das Spiel zu Ende. Ziel des Spiels ist es, so viele Panels wie möglich zu platzieren. Mit dem Button Pause kann das Spiel unterbrochen werden, eine erneute Betätigung des Buttons lässt das Spiel weiterlaufen.
11.1.2 Programmbeschreibung 

Hilfsfeld
Der Kasten, in dem sich die fallenden Panels befinden, ist 8 Spalten breit und 13 Zeilen hoch. Als Hilfskonstruktion steht das zweidimensionale Feld F mit 10 Spalten und 15 Zeilen zur Verfügung, in dem jedes existierende Panel mit seiner laufenden Nummer vermerkt ist.
Ze / Sp | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
1 |
–2 |
–1 |
–1 |
–1 |
–1 |
–1 |
–1 |
–1 |
–1 |
–2 |
2 |
–2 |
–1 |
–1 |
–1 |
–1 |
–1 |
–1 |
–1 |
–1 |
–2 |
3 |
–2 |
–1 |
–1 |
–1 |
–1 |
–1 |
–1 |
–1 |
–1 |
–2 |
4 |
–2 |
–1 |
–1 |
–1 |
–1 |
–1 |
–1 |
–1 |
–1 |
–2 |
5 |
–2 |
–1 |
–1 |
–1 |
–1 |
–1 |
–1 |
–1 |
–1 |
–2 |
6 |
–2 |
–1 |
–1 |
–1 |
–1 |
–1 |
–1 |
–1 |
–1 |
–2 |
7 |
–2 |
–1 |
–1 |
–1 |
–1 |
–1 |
–1 |
–1 |
–1 |
–2 |
8 |
–2 |
–1 |
–1 |
–1 |
–1 |
–1 |
–1 |
–1 |
–1 |
–2 |
9 |
–2 |
–1 |
–1 |
–1 |
–1 |
–1 |
–1 |
–1 |
–1 |
–2 |
10 |
–2 |
–1 |
–1 |
–1 |
–1 |
–1 |
–1 |
–1 |
–1 |
–2 |
11 |
–2 |
–1 |
–1 |
–1 |
11 |
–1 |
–1 |
–1 |
–1 |
–2 |
12 |
–2 |
–1 |
–1 |
–1 |
3 |
8 |
9 |
–1 |
–1 |
–2 |
13 |
–2 |
–1 |
0 |
10 |
2 |
4 |
5 |
–1 |
–1 |
–2 |
14 |
–2 |
–2 |
–2 |
–2 |
–2 |
–2 |
–2 |
–2 |
–2 |
–2 |
Im Beispiel in Tabelle 11.1 wird der Inhalt des Felds F nach den Panels 0 bis 11, also nach 12 gefallenen Panels angezeigt. Die Panels 1, 6 und 7 hatten die gleiche Farbe, standen über- oder nebeneinander und sind deshalb schon verschwunden. Die Randelemente werden zu Spielbeginn mit dem Wert der Konstanten Rand=-2 besetzt. Alle Elemente des Feldes F, die kein Panel enthalten, also leer sind, haben den Wert der Konstanten Leer=-1.
11.1.3 Steuerelemente 

Es gibt zu Beginn des Programms folgende Steuerelemente:
- vier Buttons für Links, Rechts, Drop und Pause
- drei Panels als Begrenzungslinien des Spielfelds
Timer
- einen Timer, der das aktuelle Panel automatisch weiter fallen lässt (Startwert für den Zeitintervall: 500ms).
Im Verlauf des Programms werden weitere Steuerelemente vom Typ Panel hinzugefügt bzw. wieder entfernt.
11.1.4 Initialisierung des Programms 

Zu Beginn des Programms werden die klassenweit gültigen Variablen und Konstanten vereinbart und die Form1_Load-Prozedur durchlaufen:
Public Class Form1 ' Index des aktuellen Panels Dim PX As Integer ' Gesamtes Spielfeld inkl. Randfelder Dim F(14, 9) As Integer ' Zeile und Spalte des aktuellen Panels Dim PZ As Integer Dim PS As Integer ' Schwierigkeitsstufe Dim Stufe As Integer ' Eine zunächst leere Liste von Spiel-Panels Dim PL As New ArrayList ' Ein Feld von Farben für die Panels Dim FarbenFeld() As Color = {Color.Red, Color.Yellow, Color.Green, Color.Blue, Color.Cyan, Color.Magenta, Color.Black, Color.White} ' Konstanten für Status eines Feldpunktes Const Leer = –1 Const Rand = –2 Private Sub Form1_Load(...) Handles MyBase.Load Dim Z, S As Integer ' Zufallsgenerator initialisieren Randomize() ' Feld besetzen For Z = 1 To 13 F(Z, 0) = Rand For S = 1 To 8 F(Z, S) = Leer Next S F(Z, 9) = Rand Next Z For S = 0 To 9 F(14, S) = Rand Next S ' Initialisierung Stufe = 1 NächstesPanel() End Sub [...] End Class
Listing 11.1 Projekt »Tetris«, Variablen, Konstanten, Start
Zur Erläuterung der klassenweit gültigen Variablen und Konstanten:
- Die laufende Nummer (der Index) des aktuell fallenden Panels wird in der Variablen PX festgehalten.
Hilfsfeld
- Das gesamte Spielfeld, das in Abschnitt 11.1.2 schematisch dargestellt wurde, wird im zweidimensionalen Feld F gespeichert.
- Die Variablen PZ und PS beinhalten die Zeilen- und Spalten-Position des aktuell fallenden Panels innerhalb des Spielfelds.
Level
- Die Variable Stufe kennzeichnet den Schwierigkeitsgrad des Spiels. Jedes Mal, wenn drei Panels, die untereinander oder nebeneinander lagen, gelöscht wurden, wird die Stufe um 1 erhöht. Dies sorgt für ein kürzeres Timer-Intervall, die Panels werden schneller.
Liste von Panels
- PL ist eine ArrayList von Steuerelementen vom Typ Panel. ArrayLists können beliebige Objekte enthalten. Dies können Variablen, Objekte eigener Klassen oder, wie hier, Steuerelemente, also Objekte vorhandener Klassen sein. Zu Beginn ist die ArrayList leer.
- Das Feld FarbenFeld enthält insgesamt acht Farben. Die Farben der Panels werden per Zufallsgenerator ermittelt.
- Die Konstanten Leer und Rand werden erzeugt. Die Namen der Konstanten sind im Programm leichter lesbar als die Werte –1 bzw. –2.
Zur Erläuterung der Form1_Load-Prozedur:
Zufallsgenerator
- Es wird zunächst der Zufallsgenerator initialisiert.
- Anschließend werden die Elemente des oben beschriebenen Hilfsfelds F mit Leer bzw. Rand besetzt.
- Die Schwierigkeitsstufe wird auf 1 gesetzt.
Erstes Panel
- Es wird die Prozedur NächstesPanel() aufgerufen. Sie ist in diesem Fall für die Erzeugung des ersten fallenden Panels zuständig.
11.1.5 Erzeugen eines neuen Panels 

Die Prozedur NächstesPanel() dient zur Erzeugung eines neuen fallenden Panels. Dies geschieht zu Beginn des Spiels und nachdem ein Panel auf dem unteren Rand des Spielfelds oder auf einem anderen Panel zum Stehen gekommen ist.
Public Class Form1 [...] Private Sub NächstesPanel() Dim Farbe As Integer Dim p As New Panel ' Neues Panel zur ArrayList hinzufügen PL.Add(p) ' Eventhandler für Event 'Click' zuweisen AddHandler p.Click, AddressOf PanelClickReaktion ' Neues Panel platzieren p.Location = New Point(100, 80) p.Size = New Point(20, 20) ' Farbauswahl für neues Panel Farbe = Math.Floor(Rnd() * 8) p.BackColor = FarbenFeld(Farbe) ' Neues Panel zum Formular hinzufügen Controls.Add(p) ' Index für späteren Zugriff ermitteln PX = PL.Count – 1 ' Index als Info zu Panel hinzufügen p.Tag = PX ' Aktuelle Zeile, Spalte PZ = 1 PS = 5 End Sub [...] End Class
Listing 11.2 Projekt »Tetris«, Prozedur »NächstesPanel«
Zur Erläuterung:
- Es wird ein Objekt vom Typ Panel neu erzeugt.
Neues Listenelement
- Damit darauf auch außerhalb der Prozedur zugegriffen werden kann, wird ein Verweis auf dieses Panel mithilfe der Methode Add() der ArrayList PL hinzugefügt.
Eventhandler
- Zu dem neuen Panel wird mithilfe von AddHandler ein Eventhandler zum Ereignis Benutzer klickt auf Panel hinzugefügt. Dies ist für den Spielablauf nicht notwendig, soll aber zeigen, wie man Ereignisse zu dynamisch erzeugten Steuerelementen hinzufügen kann. Der Eventhandler verweist auf die Prozedur PanelClickReaktion(), die im nächsten Abschnitt erläutert wird.
- Es werden die Eigenschaften Ort, Größe und Farbe des neuen Panels bestimmt.
Neues Steuerelement
- Das Panel wird mithilfe der Methode Add() zu der Collection Controls hinzugefügt. Dies ist eine Liste der Steuerelemente des Formulars. Dadurch wird das Panel sichtbar.
- Seine laufende Nummer (der Index) wird mithilfe der Eigenschaft Count ermittelt. Diese Nummer wird für den späteren Zugriff benötigt.
Eigenschaft Tag
- Die Eigenschaft Tag eines Steuerelements kann zur Speicherung beliebiger, nicht sichtbarer Informationen genutzt werden. In diesem Falle wird der soeben ermittelte Index zum Panel hinzugefügt. Dies ist ebenfalls für den Spielablauf nicht notwendig, wird aber für die Prozedur zum Ereignis Benutzer klickt auf Panel benötigt.
- Die Variablen PZ und PS, die die Position des aktuell fallenden Panels im Spielfeld F angeben, werden gesetzt.
11.1.6 Benutzer klickt auf Panel 

In der Prozedur PanelClickReaktion() wird die Reaktion auf das entsprechende Ereignis festgehalten:
Public Class Form1 [...] Private Sub PanelClickReaktion( ByVal sender As System.Object, ByVal e As System.EventArgs) ' Verweis auf Panel Dim p As Panel ' Verweis auf geklicktes Panel gesetzt p = sender ' Eigenschaften des geklickten Panels ändern lblPNr.Text = "P " & p.Tag p.BorderStyle = BorderStyle.FixedSingle End Sub [...] End Class
Listing 11.3 Projekt »Tetris«, Prozedur »PanelClickReaktion«
Zur Erläuterung:
- Der Kopf der Prozedur wird wie gewohnt notiert.
- Es wird ein Verweis auf ein Objekt des Typs Panel deklariert.
- Dieser Verweis wird anschließend auf das sendende Objekt gesetzt, also auf das Panel, das geklickt wurde.
- Damit ist es möglich, auf die Eigenschaften des betreffenden Panels lesend bzw. schreibend zuzugreifen. In diesem Falle wird der in der Eigenschaft Tag gespeicherte Index des Panels ausgegeben und die Form des Randes geändert.
11.1.7 Der Zeitgeber 

In regelmäßigen Zeitabständen wird das Timer-Ereignis erzeugt und damit die Ereignisprozedur timT_Tick() aufgerufen. Diese sorgt dafür, dass sich das aktuelle Panel nach unten bewegt, falls dies noch möglich ist.
Public Class Form1 [...] Private Sub timT_Tick(...) Handles timT.Tick ' Falls es nicht mehr weiter geht If F(PZ + 1, PS) <> Leer Then ' Oberste Zeile erreicht If PZ = 1 Then timT.Enabled = False MessageBox.Show("Das war's") Exit Sub End If F(PZ, PS) = PX ' Belegen AllePrüfen() NächstesPanel() Else ' Falls es noch weiter geht PL(PX).Top = PL(PX).Top + 20 PZ = PZ + 1 End If End Sub [...] End Class
Listing 11.4 Projekt »Tetris«, Zeitgeber
Zur Erläuterung:
- Zunächst wird geprüft, ob sich unterhalb des aktuellen Panels noch ein freies Feld befindet.
- Ist dies nicht der Fall, so hat das Panel seine Endposition erreicht.
Endposition
- Befindet sich diese Endposition in der obersten Zeile, so ist das Spiel zu Ende. Der Timer wird deaktiviert, anderenfalls würden weitere Panels erzeugt. Es erscheint eine Meldung, und die Prozedur wird unmittelbar beendet. Will der Spieler erneut beginnen, so muss er das Programm beenden und neu starten.
- Befindet sich die Endposition nicht in der obersten Zeile, so wird die Panelnummer im Feld F mit der aktuellen Zeile und Spalte vermerkt. Dies dient der Kennzeichnung eines belegten Feldelements.
Prüfen
- Die Prozedur AllePrüfen() wird aufgerufen (siehe unten), um festzustellen, ob es drei gleichfarbige Panels über- oder nebeneinander gibt. Anschließend wird das nächste Panel erzeugt.
Weiter fallen
- Befindet sich unterhalb des Panels noch ein freies Feld, so kann das Panel weiter fallen. Seine Koordinaten und die aktuelle Zeilennummer werden verändert.
11.1.8 Panel löschen 

Rekursive Prozedur
Die Prozedur AllePrüfen() ist eine rekursive Prozedur, mit deren Hilfe festgestellt wird, ob es drei gleichfarbige Panels nebeneinander oder übereinander gibt. Ist dies der Fall, werden diese Panels entfernt und die darüber liegenden Panels rutschen nach. Eventuell befinden sich nun wieder drei gleichfarbige Panels neben- oder übereinander, es muss also erneut geprüft werden. Dies geschieht so lange, bis keine drei gleichfarbigen Panels neben- oder übereinander gefunden wurden.
Die Prozedur AllePrüfen() bedient sich intern der beiden Funktionen NebenPrüfen() und ÜberPrüfen().
Public Class Form1 [...] Private Sub AllePrüfen() Dim Z, S As Integer Dim Neben, Über As Boolean Neben = False Über = False ' Drei gleiche Panels nebeneinander ? For Z = 13 To 1 Step –1 For S = 1 To 6 Neben = NebenPrüfen(Z, S) If Neben Then Exit For Next S If Neben Then Exit For Next Z ' Drei gleiche Panels übereinander ? For Z = 13 To 3 Step –1 For S = 1 To 8 Über = ÜberPrüfen(Z, S) If Über Then Exit For Next S If Über Then Exit For Next Z If Neben Or Über Then ' Schneller Stufe = Stufe + 1 timT.Interval = 5000 / (Stufe + 9) ' Eventuell kann jetzt noch eine Reihe ' entfernt werden AllePrüfen() End If End Sub ' Falls 3 Felder nebeneinander besetzt Private Function NebenPrüfen(ByVal Z As Integer, ByVal S As Integer) As Boolean Dim ZX, SX As Integer NebenPrüfen = False If F(Z, S) <> Leer And F(Z, S + 1) <> Leer And F(Z, S + 2) <> Leer Then ' Falls drei Farben gleich If PL(F(Z, S)).BackColor = PL(F(Z, S + 1)).BackColor And PL(F(Z, S)).BackColor = PL(F(Z, S + 2)).BackColor Then For SX = S To S + 2 ' PL aus dem Formular löschen Controls.Remove(PL(F(Z, SX))) ' Feld leeren F(Z, SX) = Leer ' Panels oberhalb des entladenen ' Panels absenken ZX = Z – 1 Do While F(ZX, SX) <> Leer PL(F(ZX, SX)).Top = PL(F(ZX, SX)).Top + 20 ' Feld neu besetzen F(ZX + 1, SX) = F(ZX, SX) F(ZX, SX) = Leer ZX = ZX – 1 Loop Next SX NebenPrüfen = True End If End If End Function ' Falls drei Felder übereinander besetzt Private Function ÜberPrüfen(ByVal Z As Integer, ByVal S As Integer) As Boolean Dim ZX As Integer ÜberPrüfen = False If F(Z, S) <> Leer And F(Z – 1, S) <> Leer And F(Z – 2, S) <> Leer Then ' Falls drei Farben gleich If PL(F(Z, S)).BackColor = PL(F(Z – 1, S)).BackColor And PL(F(Z, S)).BackColor = PL(F(Z – 2, S)).BackColor Then ' 3 Panels entladen For ZX = Z To Z – 2 Step –1 ' PL aus dem Formular löschen Controls.Remove(PL(F(ZX, S))) ' Feld leeren F(ZX, S) = Leer Next ZX ÜberPrüfen = True End If End If End Function End Class [...]
Listing 11.5 Projekt »Tetris«, Panel löschen
Zur Erläuterung:
- Die Variablen Neben und Über kennzeichnen die Tatsache, dass drei gleichfarbige Panels neben- oder übereinander gefunden wurden. Sie werden zunächst auf False gesetzt.
Nebeneinander
- Zunächst wird geprüft, ob sich drei gleichfarbige Panels nebeneinander befinden. Dies geschieht, indem für jedes einzelne Feldelement in der Funktion NebenPrüfen() geprüft wird, ob es selbst und seine beiden rechten Nachbarn mit einem Panel belegt sind, und ob diese Panels gleichfarbig sind. Die Prüfung beginnt beim Panel unten links und setzt sich bis zum drittletzten Panel der gleichen Zeile fort. Anschließend werden die Panels in der Zeile darüber geprüft usw.
Panels löschen
- Sobald eine Reihe gleichfarbiger Panel gefunden wurde, werden alle drei Panels mithilfe der Methode Remove() aus der Collection der Steuerelemente des Formulars gelöscht, d. h. sie verschwinden aus dem Formular. Ihre Position im Feld F wird mit –1 (=Leer) besetzt. Nun müssen noch alle Panels, die sich eventuell oberhalb der drei Panels befinden, um eine Position abgesenkt werden. Die Variable Neben wird auf True gesetzt. Die doppelte Schleife wird sofort verlassen.
Übereinander
- Analog wird nun in der Funktion ÜberPrüfen() geprüft, ob sich drei gleichfarbige Panels übereinander befinden. Ist dies der Fall, so werden sie aus der Collection der Steuerelemente des Formulars gelöscht. Ihre Positionen im Feld F werden mit –1 besetzt. Über den drei Panels können sich keine weiteren Panels befinden, die entfernt werden müssten.
Rekursiv
- Falls durch eine der beiden Prüfungen eine Reihe gefunden und entfernt wurde, so wird die Schwierigkeitsstufe erhöht und das Timer-Intervall verkürzt. Nun muss geprüft werden, ob sich durch das Nachrutschen von Panels wiederum ein Bild mit drei gleichfarbigen Panels über- oder nebeneinander ergeben hat. Die Prozedur AllePrüfen() ruft sich also so lange selbst auf (rekursive Prozedur), bis keine Reihe mehr gefunden wurde.
11.1.9 Panel seitlich bewegen 

Mithilfe der beiden Ereignisprozeduren cmdLinks_Click() und cmdRechts_Click() werden die Panels nach links bzw. rechts bewegt, falls dies möglich ist.
Public Class Form1 [...] Private Sub cmdLinks_Click(...) Handles ... If F(PZ, PS – 1) = Leer Then PL(PX).Left = PL(PX).Left – 20 PS = PS – 1 End If End Sub Private Sub cmdRechts_Click(...) Handles ... If F(PZ, PS + 1) = Leer Then PL(PX).Left = PL(PX).Left + 20 PS = PS + 1 End If End Sub [...] End Class
Listing 11.6 Projekt »Tetris«, Panel seitlich bewegen
Zur Erläuterung:
Seitlich
- Es wird geprüft, ob sich links bzw. rechts vom aktuellen Panel ein freies Feldelement befindet. Ist dies der Fall, so wird das Panel nach links bzw. rechts verlegt und die aktuelle Spaltennummer verändert.
11.1.10 Panel nach unten bewegen 

Die Ereignisprozedur cmdUnten_Click() dient der wiederholten Bewegung der Panels nach unten, falls dies möglich ist. Diese Bewegung wird so lange durchgeführt, bis das Panel auf die Spielfeldbegrenzung oder ein anderes Panel stößt.
Public Class Form1 [...] Private Sub cmdUnten_Click(...) Handles ... Do While F(PZ + 1, PS) = Leer PL(PX).Top = PL(PX).Top + 20 PZ = PZ + 1 Loop F(PZ, PS) = PX 'Belegen AllePrüfen() NächstesPanel() End Sub [...] End Class
Listing 11.7 Projekt »Tetris«, Panel nach unten bewegen
Zur Erläuterung:
Nach unten
- Es wird geprüft, ob sich unter dem aktuellen Panel ein freies Feldelement befindet. Ist dies der Fall, so wird das Panel nach unten verlegt und die aktuelle Zeilennummer verändert. Dies geschieht so lange, bis das Panel auf ein Hindernis stößt.
- Anschließend wird das betreffende Feldelement belegt. Es wird geprüft, ob nun eine neue Reihe von drei gleichfarbigen Panels existiert und das nächste Panel wird erzeugt.
11.1.11 Pause 

Spiel anhalten
Abhängig vom aktuellen Zustand wird durch Betätigen des Buttons Pause in den Zustand Pause geschaltet oder wieder zurück.
Public Class Form1 [...] Private Sub cmdPause_Click(...) Handles ... timT.Enabled = Not timT.Enabled End Sub End Class
Listing 11.8 Projekt »Tetris«, Pause
Zur Erläuterung:
- Der Zustand des Timers wechselt zwischen Enabled = True und Enabled = False.