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.
Um zunächst bei den Äußerlichkeiten zu bleiben:
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):
Sollten jedoch zwei Minen irgendwie nebeneinander liegen, verändert sich das Bild natürlich:
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:
Diese Bilder laden wir in Bildfelder (PictureBox: picMine und picTime). Jetzt müssen folgende Eigenschaften für die Bildfelder 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
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:
Ist das alles erledigt sollte das Ganze so aussehen:
API-Funktionen: Das Modul mDeclarations Nun geht es ans Eingemachte, den CODE! 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.
Anmerkung: Dieser Workshop wurde bereits 15.718 mal aufgerufen.
Anzeige
![]() ![]() ![]() (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. |
Access-Tools Vol.1 ![]() Über 400 MByte Inhalt Mehr als 250 Access-Beispiele, 25 Add-Ins und ActiveX-Komponenten, 16 VB-Projekt inkl. Source, mehr als 320 Tipps & Tricks für Access und VB Tipp des Monats ![]() Dieter Otter PopUp-Menü wird nicht angezeigt :-( In diesem Tipp verraten wir Ihnen, wie Sie Probleme mit PopUp-Menüs umgehen können, wenn diese unter bestimmten Umständen einfach nicht angezeigt werden. sevOutBar 4.0 ![]() Vertikale Menüleisten á la Outlook Erstellen von Outlook ähnlichen Benutzer- interfaces - mit beliebig vielen Gruppen und Symboleinträgen. Moderner OfficeXP-Style mit Farbverläufen, Balloon-Tips, u.v.m. |
||||||||||||||||||||||||||||||||||||
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. |