api:projects:minesweeper:start
Unterschiede
Hier werden die Unterschiede zwischen zwei Versionen angezeigt.
Beide Seiten der vorigen RevisionVorhergehende ÜberarbeitungNächste Überarbeitung | Vorhergehende Überarbeitung | ||
api:projects:minesweeper:start [2020/12/28 15:35] – Martin Pabst | api:projects:minesweeper:start [2021/12/29 11:29] (aktuell) – Externe Bearbeitung 127.0.0.1 | ||
---|---|---|---|
Zeile 1: | Zeile 1: | ||
====== Minesweeper ====== | ====== Minesweeper ====== | ||
- | {{ : | + | {{ : |
Im Spiele-Klassiker Minesweeper versteckt der Computer in einem Spielfeld aus kleinen quadratischen Feldern hinter einzelnen Felder Minen. Der Spieler hat die Aufgabe, die verminten Felder mit Fahnen zu markieren. Er kann einzelne Felder aufdecken (linke Maustaste), hat aber verloren, wenn er auf eine Mine stößt. Als Hinweis dafür, wo sich die Minen befinden, schreibt der Computer in jedes aufgedeckte Feld die Anzahl der Minen, die sich in den acht angrenzenden Felder befinden. Ist sich der Spieler sicher, dass sich in einem Feld eine Mine befindet, so kann er mit der rechten Maustaste eine Fahne setzen bzw. wieder löschen. Der Computer stellt ihm aber nur so viele Fahnen zur Verfügung, wie er Minen versteckt hat. \\ \\ | Im Spiele-Klassiker Minesweeper versteckt der Computer in einem Spielfeld aus kleinen quadratischen Feldern hinter einzelnen Felder Minen. Der Spieler hat die Aufgabe, die verminten Felder mit Fahnen zu markieren. Er kann einzelne Felder aufdecken (linke Maustaste), hat aber verloren, wenn er auf eine Mine stößt. Als Hinweis dafür, wo sich die Minen befinden, schreibt der Computer in jedes aufgedeckte Feld die Anzahl der Minen, die sich in den acht angrenzenden Felder befinden. Ist sich der Spieler sicher, dass sich in einem Feld eine Mine befindet, so kann er mit der rechten Maustaste eine Fahne setzen bzw. wieder löschen. Der Computer stellt ihm aber nur so viele Fahnen zur Verfügung, wie er Minen versteckt hat. \\ \\ | ||
- | Hat der Spieler alle Felder des Spielfeldes entweder aufgedeckt oder mit Fahnen markiert, so hat ist das Spiel gewonnen. | + | Hat der Spieler alle Felder des Spielfeldes entweder aufgedeckt oder mit Fahnen markiert, so hat ist das Spiel gewonnen. |
+ | Etwas weiter unten findest Du das gesamte Spiel zum Ausprobieren und verändern. Ich habe die einzelnen Entwicklungsschritte dokumentiert und mit ausführlichen Erklärungen versehen: | ||
+ | * [[.zelle: | ||
+ | * [[.spielfeld: | ||
+ | * Die Klasse Minesweeper | ||
+ | |||
+ | |||
+ | < | ||
+ | <div class=" | ||
+ | |||
+ | <script type=" | ||
+ | // Hauptprogramm | ||
+ | new World(645, 800); | ||
+ | |||
+ | // Der Konstruktor der Klasse Minesweeper initialisiert das Spiel, | ||
+ | // danach wird die Anwendung durch Ereignisse (click, mouseup) und die | ||
+ | // act-Methode von Minesweeper weiter vorangebracht. | ||
+ | new Minesweeper(); | ||
+ | |||
+ | // Mit dem Aufruf der Methode setZustand der Klasse Minesweeper wird der | ||
+ | // Übergang von einem Zustand zum nächsten herbeigeführt. | ||
+ | enum Zustand { | ||
+ | | ||
+ | } | ||
+ | |||
+ | // Hauptklasse der Anwendung | ||
+ | class Minesweeper extends Actor { | ||
+ | |||
+ | | ||
+ | |||
+ | // Der linke rote Zähler zeigt die Differenz aus der Anzahl der Minen und | ||
+ | // der Flaggen. | ||
+ | | ||
+ | |||
+ | // Der rechte rote Zähler zählt die verstrichenen Sekunden. | ||
+ | | ||
+ | |||
+ | // Die Smiley-Grafik in der Mitte | ||
+ | | ||
+ | |||
+ | // Zustand, in dem sich das Spiel gerade befindet | ||
+ | | ||
+ | |||
+ | // Text am unteren Rand des Grafikbereichs zur Ausgabe von Status- und Hilfstexten | ||
+ | | ||
+ | |||
+ | // Zähler, mit dem jeweils 30 verstrichene Frames (d.h. 1 Sekunde) gezählt werden. | ||
+ | // Je 30 Frames wird eine Sekunde hochgezählt. | ||
+ | | ||
+ | |||
+ | | ||
+ | super(); | ||
+ | |||
+ | spielfeld = new Spielfeld(20, | ||
+ | spielfeld.init(); | ||
+ | |||
+ | text = new Text(322, 750, 32, "" | ||
+ | text.setAlignment(Alignment.center); | ||
+ | text.setFillColor(Color.white); | ||
+ | |||
+ | this.smiley = new Smiley(305, 50, this); | ||
+ | setZustand(Zustand.spiel_läuft); | ||
+ | } | ||
+ | |||
+ | | ||
+ | // Zähle die Frames: | ||
+ | if(--frameCounter <= 0) { | ||
+ | // 30 Frames (d.h. 1 Sekunde) sind verstrichen | ||
+ | | ||
+ | | ||
+ | spielfeld.zufälligeZelleKlicken(); | ||
+ | } else if(zustand == Zustand.spiel_läuft) { | ||
+ | sekundenCounter.add(1); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | | ||
+ | return minenCounter; | ||
+ | } | ||
+ | |||
+ | | ||
+ | return zustand; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Alle Zustandsübergänge werden durch Aufrufen dieser Methode herbeigeführt. | ||
+ | */ | ||
+ | | ||
+ | |||
+ | zustand = neuerZustand; | ||
+ | |||
+ | switch(neuerZustand) { | ||
+ | case Zustand.intro : // intro | ||
+ | text.setText(" | ||
+ | break; | ||
+ | case Zustand.spiel_läuft : // Spiel läuft | ||
+ | text.setText("" | ||
+ | spielfeld.löschen(); | ||
+ | spielfeld.init(); | ||
+ | sekundenCounter.set(0); | ||
+ | minenCounter.set(spielfeld.getMinenAnzahl()); | ||
+ | smiley.showSmile(); | ||
+ | break; | ||
+ | case Zustand.spiel_gewonnen : // Spiel gewonnen | ||
+ | smiley.showFaceWithSunglasses(); | ||
+ | text.setText(" | ||
+ | break; | ||
+ | case Zustand.spiel_verloren : // Spiel verloren | ||
+ | smiley.showCryingFace(); | ||
+ | spielfeld.allesAufdecken(); | ||
+ | text.setText(" | ||
+ | break; | ||
+ | default : | ||
+ | |||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Durch Klick auf den Smiley kann ein neues Spiel gestartet werden. Diese | ||
+ | * Methode wird durch die Methode Smiley.onMouseDown aufgerufen. | ||
+ | */ | ||
+ | | ||
+ | if(zustand != Zustand.spiel_läuft) { | ||
+ | | ||
+ | } | ||
+ | } | ||
+ | |||
+ | } | ||
+ | </ | ||
+ | |||
+ | <script type=" | ||
+ | /** | ||
+ | * Ein Objekt der Klasse Spielfeld enthält und verwaltet alle Zellen. | ||
+ | */ | ||
+ | class Spielfeld { | ||
+ | |||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | |||
+ | | ||
+ | this.spalten = spalten; | ||
+ | this.zeilen = zeilen; | ||
+ | this.minesweeper = minesweeper; | ||
+ | this.minenAnzahl = minenAnzahl; | ||
+ | zellen = new Zelle[spalten][zeilen]; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Instanziere alle Zellen und " | ||
+ | */ | ||
+ | | ||
+ | // Erzeuge zunächst Zellen ohne Minen. | ||
+ | for(int spalte = 0; spalte < spalten; spalte++) { | ||
+ | | ||
+ | |||
+ | zellen[spalte][zeile] = new Zelle(this, ZellBild.hintergrund_leer, | ||
+ | |||
+ | } | ||
+ | } | ||
+ | |||
+ | // " | ||
+ | for(int i = 0; i < minenAnzahl; | ||
+ | | ||
+ | | ||
+ | int spalte = Math.floor(Math.random() * spalten); | ||
+ | int zeile = Math.floor(Math.random() * zeilen); | ||
+ | Zelle zelle = zellen[spalte][zeile]; | ||
+ | if(zelle.getInhalt() != ZellBild.mine) { | ||
+ | | ||
+ | | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | |||
+ | // Zähle für jede nicht-Minen-Zelle die Nachbarminen und stelle die | ||
+ | // Anzahl als Zahlengrafik dar. | ||
+ | for(int spalte = 0; spalte < spalten; spalte++) { | ||
+ | | ||
+ | |||
+ | Zelle zelle = zellen[spalte][zeile]; | ||
+ | if(zelle.getInhalt() == ZellBild.hintergrund_leer) { | ||
+ | int nachbarbomben = zähleNachbarMinen(zelle); | ||
+ | | ||
+ | zelle.setZustand(ZellBild.zahl_0 + nachbarbomben, | ||
+ | } | ||
+ | } | ||
+ | |||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | | ||
+ | return minesweeper; | ||
+ | } | ||
+ | |||
+ | | ||
+ | return minenAnzahl; | ||
+ | } | ||
+ | |||
+ | | ||
+ | int nachbarMinen = 0; | ||
+ | for(int dx = -1; dx <= 1; dx++) { | ||
+ | | ||
+ | if(dx != 0 || dy != 0) { | ||
+ | int spalte = zelle.getSpalte() + dx; | ||
+ | int zeile = zelle.getZeile() + dy; | ||
+ | | ||
+ | if(zellen[spalte][zeile].getInhalt() == ZellBild.mine) { | ||
+ | | ||
+ | }; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | return nachbarMinen; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Zerstört alle Zellen des Spielfelds | ||
+ | */ | ||
+ | | ||
+ | for(int spalte = 0; spalte < spalten; spalte++) { | ||
+ | | ||
+ | |||
+ | zellen[spalte][zeile].destroy(); | ||
+ | zellen[spalte][zeile] = null; | ||
+ | |||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Berechnet die x-Koordinate des Pixels am linken Rand der übergebenen Spalte. | ||
+ | */ | ||
+ | | ||
+ | return 21 + 32 * spalte; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Berechnet die y-Koordinate des Pixels am oberen Rand der übergebenen Zeile. | ||
+ | */ | ||
+ | | ||
+ | return 116 + 32 * zeile; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Wird von Zelle.onMouseUp aufgerufen und deckt die Zelle auf. | ||
+ | * Enthält sie keine Mine und enthalten auch die Nachbarzellen keine Minen, so werden | ||
+ | * rekursiv alle benachbarten Zellen aufgedeckt, die ebenfalls keine Minen enthalten | ||
+ | * und deren Nachbarzellen ebenfalls keine Minen enthalten. | ||
+ | */ | ||
+ | | ||
+ | zelle.aufdecken(); | ||
+ | if(zelle.getInhalt() == ZellBild.hintergrund_leer) { | ||
+ | // Falls weder die Zelle noch die Nachbarzellen Minen enthalten, | ||
+ | // dann untersuche die acht benachbarten Zellen: | ||
+ | | ||
+ | for(int dy = -1; dy <= 1; dy++) { | ||
+ | if(dx != 0 || dy != 0) { | ||
+ | int spalte = zelle.getSpalte() + dx; | ||
+ | int zeile = zelle.getZeile() + dy; | ||
+ | if(spalte >= 0 && spalte < spalten && zeile >= 0 && zeile < zeilen) { | ||
+ | // Hole das benachbarte Zellen-Objekt | ||
+ | Zelle zelle = zellen[spalte][zeile]; | ||
+ | | ||
+ | klickAufZelle(zelle); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Fürs Intro: Zufällige Zelle öffnen | ||
+ | */ | ||
+ | | ||
+ | int spalte = Math.floor(Math.random() * spalten); | ||
+ | int zeile = Math.floor(Math.random() * zeilen); | ||
+ | klickAufZelle(zellen[spalte][zeile]); | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Markiere die Zelle mit einer Fahne | ||
+ | */ | ||
+ | | ||
+ | if(minesweeper.getMinenCounter().getNumber() > 0) { | ||
+ | | ||
+ | | ||
+ | } | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Entferne die Fahnen-Markierung der Zelle | ||
+ | */ | ||
+ | | ||
+ | zelle.setZustand(zelle.getInhalt(), | ||
+ | minesweeper.getMinenCounter().add(1); | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Nachdem das Spiel verloren ist, werden alle Zellen aufgedeckt. | ||
+ | */ | ||
+ | | ||
+ | for(int spalte = 0; spalte < spalten; spalte++) { | ||
+ | | ||
+ | |||
+ | Zelle zelle = zellen[spalte][zeile]; | ||
+ | if(zelle.istZugedeckt()) { | ||
+ | | ||
+ | if(zelle.getInhalt() == ZellBild.mine) { | ||
+ | | ||
+ | } else { | ||
+ | | ||
+ | } | ||
+ | } else { | ||
+ | zelle.aufdecken(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | } | ||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Gibt genau dann true zurück, wenn alle Minen markiert und alle anderen | ||
+ | * Zellen aufgedeckt sind. | ||
+ | */ | ||
+ | | ||
+ | for(int spalte = 0; spalte < spalten; spalte++) { | ||
+ | | ||
+ | Zelle zelle = zellen[spalte][zeile]; | ||
+ | if(zelle.istZugedeckt() && !zelle.hatFahne()) return false; | ||
+ | } | ||
+ | } | ||
+ | return true; | ||
+ | } | ||
+ | |||
+ | } | ||
+ | </ | ||
+ | |||
+ | <script type=" | ||
+ | // Indizes der Sprites für die verschiedenen Zellbilder | ||
+ | class ZellBild { | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Die Klasse Zelle repräsentiert eine quadratische Zelle des Spielfeldes. Sie wird mithilfe von | ||
+ | * zwei Sprites gezeichnet: dem Hintergrund (Zell-Objekt ist selbst ein Sprite) und dem Vordergrund | ||
+ | * (gleichnamiges Attribut). | ||
+ | */ | ||
+ | class Zelle extends Sprite { | ||
+ | |||
+ | | ||
+ | |||
+ | | ||
+ | | ||
+ | |||
+ | | ||
+ | |||
+ | // Jede Zelle " | ||
+ | // öffentliche Methoden aufrufen. | ||
+ | | ||
+ | |||
+ | | ||
+ | | ||
+ | |||
+ | | ||
+ | super(spielfeld.spalteToX(spalte), | ||
+ | vordergrund = new Sprite(spielfeld.spalteToX(spalte), | ||
+ | this.spielfeld = spielfeld; | ||
+ | this.spalte = spalte; | ||
+ | this.zeile = zeile; | ||
+ | setZustand(zustand, | ||
+ | } | ||
+ | |||
+ | | ||
+ | return inhalt; | ||
+ | } | ||
+ | |||
+ | | ||
+ | return zeile; | ||
+ | } | ||
+ | |||
+ | | ||
+ | return spalte; | ||
+ | } | ||
+ | |||
+ | | ||
+ | return zugedeckt; | ||
+ | } | ||
+ | |||
+ | | ||
+ | return fahne; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Ändert sich der Zustand der Zelle, so sorgt die Methode setZustand dafür, | ||
+ | * dass die Bilder der Sprites entsprechend gesetzt werden. | ||
+ | */ | ||
+ | | ||
+ | if(zugedeckt) { | ||
+ | | ||
+ | vordergrund.setImageIndex(ZellBild.fahne); | ||
+ | } else { | ||
+ | vordergrund.setImageIndex(ZellBild.zugedeckt); | ||
+ | } | ||
+ | } else { | ||
+ | | ||
+ | } | ||
+ | this.zugedeckt = zugedeckt; | ||
+ | this.fahne = fahne; | ||
+ | this.inhalt = inhalt; | ||
+ | } | ||
+ | |||
+ | | ||
+ | super.destroy(); | ||
+ | vordergrund.destroy(); | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Wird vom Browser aufgerufen, wenn der Mauszeiger sich von außen in die Zelle hineinbewegt | ||
+ | */ | ||
+ | | ||
+ | if(zugedeckt && getSpielZustand() == Zustand.spiel_läuft) { | ||
+ | | ||
+ | | ||
+ | } | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Wird vom Browser aufgerufen, wenn der Mauszeiger sich aus der Zelle | ||
+ | * hinausbewegt. | ||
+ | */ | ||
+ | | ||
+ | vordergrund.tint("# | ||
+ | getWorld().setCursor(" | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Wird vom Browser aufgerufen, wenn eine der Maustasten nach unten gedrückt wird. | ||
+ | */ | ||
+ | | ||
+ | vordergrund.tint("# | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Wird vom Browser aufgerufen, wenn eine der Maustasten losgelassen wird. | ||
+ | */ | ||
+ | | ||
+ | if(getSpielZustand() != Zustand.spiel_läuft) return; | ||
+ | |||
+ | if(key == 0) { // linke Maustaste, also aufdecken | ||
+ | | ||
+ | // Mine erwischt? | ||
+ | | ||
+ | inhalt = ZellBild.mine_explodiert; | ||
+ | setZustand(inhalt, | ||
+ | spielfeld.getMinesweeper().setZustand(Zustand.spiel_verloren); | ||
+ | return; | ||
+ | } | ||
+ | | ||
+ | } else { | ||
+ | // Rechte Maustaste => Fahne setzen oder löschen | ||
+ | | ||
+ | if(!fahne) { | ||
+ | | ||
+ | } else { | ||
+ | | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Spiel gewonnen? | ||
+ | if(spielfeld.alleAufgedeckt()) { | ||
+ | | ||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | | ||
+ | setZustand(inhalt, | ||
+ | } | ||
+ | |||
+ | | ||
+ | return spielfeld.getMinesweeper().getZustand(); | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | <script type=" | ||
+ | /** | ||
+ | * Das Smiley-Objekt stellt den Smiley über dem Spielfeld dar. | ||
+ | */ | ||
+ | class Smiley extends Sprite { | ||
+ | |||
+ | | ||
+ | |||
+ | | ||
+ | super(x, y, SpriteLibrary.Minesweeper, | ||
+ | this.minesweeper = minesweeper; | ||
+ | scale(2); | ||
+ | } | ||
+ | |||
+ | | ||
+ | minesweeper.onSmileyKlicked(); | ||
+ | } | ||
+ | |||
+ | | ||
+ | this.setImageIndex(18); | ||
+ | } | ||
+ | |||
+ | | ||
+ | this.setImageIndex(17); | ||
+ | } | ||
+ | |||
+ | | ||
+ | this.setImageIndex(19); | ||
+ | } | ||
+ | |||
+ | | ||
+ | if(minesweeper.getZustand() != Zustand.spiel_läuft) { | ||
+ | | ||
+ | | ||
+ | } | ||
+ | } | ||
+ | |||
+ | | ||
+ | getWorld().setCursor(" | ||
+ | tint("# | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | <script type=" | ||
+ | /** | ||
+ | * Ein Objekt dieser Klasse stellt einen roten dreistelligen Zähler dar. | ||
+ | */ | ||
+ | class Counter extends Group { | ||
+ | |||
+ | | ||
+ | | ||
+ | |||
+ | | ||
+ | super(); | ||
+ | for(int i = 0; i < 3; i++) { | ||
+ | | ||
+ | } | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Stellt die übergebene Zahl auf dem Bildschirm dar. | ||
+ | */ | ||
+ | | ||
+ | this.number = number; | ||
+ | for(int i = 0; i < 3; i++) { | ||
+ | | ||
+ | | ||
+ | } | ||
+ | } | ||
+ | |||
+ | | ||
+ | return number; | ||
+ | } | ||
+ | |||
+ | | ||
+ | set(number + n); | ||
+ | } | ||
+ | |||
+ | } | ||
+ | </ | ||
+ | |||
+ | |||
+ | </ | ||
+ | |||
+ | </ | ||
api/projects/minesweeper/start.txt · Zuletzt geändert: 2021/12/29 11:29 von 127.0.0.1