Hauptinhalt
Programmierung
Kurs: Programmierung > Lerneinheit 4
Lesson 5: Ein Memoryspiel programmierenMemory: Ein Raster der Kacheln zeichnen
Im ersten Schritt von "Memory" müssen alle Kacheln zufällig gemischt und in einem rechteckigen Raster ausgelegt werden. Die Oberseite muss nach unten zeigen, damit wir die Bilder auf der anderen Seite jeder Kacheln nicht sehen können.
Kacheln mit der Oberseite nach unten
Zu Beginn der Spielprogrammierung machen wir uns erst mal nur Gedanken über die Kacheln, die mit der Oberseite nach unten zeigen. Um die Bilder kümmern wir uns später.
Die "Kachel" ist ein wichtiges Objekt für das Spiel "Memory". Daher werden wir Objektorientierte Programmierung verwenden, um das Objekt
Tile
(Kachel) zu definieren und danach mehrer Instanzen davon zu erstellen. Dann können wir mit jeder Kachel
sowohl Eigenschaften (wie Koordinate und das Bild) wie auch Methoden (wie z.B. zeichnen) verwenden.Zu Beginn definieren wir den Konstruktor für
Tile
. Da wir uns noch nicht um die Bilder kümmern, übergeben wir ihr einfach Parameter für x
und y
. Wir speichern auch die Grösse (eine Kostante) der Kachel als Eigenschaft des Objektes.var Tile = function(x, y) {
this.x = x;
this.y = y;
this.size = 50;
};
Nun da wir den Konstruktur definiert haben, können wir ihn in einer Schleife verwenden, um Kacheln an entsprechenden x- und y-Positionen anzulegen. Eigentlich verwenden wir zwei
for
-Schleifen (eine verschachtelte for
-Schleife), da es uns dies einfacher macht, Koordinaten für ein Raster zu generieren.Zuerst müssen wir ein leeres
tiles
-Array deklarieren, um alle diese Kacheln zu speichern:var tiles = [];
Unsere äußere Schleife iteriert so viele Spalten wie wir wollen, unsere innere Schleife iteriert alle Zeilen, und jede neue
Tile
wird mit einem x und einem y initialisiert, das dieser Zeile und Spalte entspricht.var NUM_COLS = 5;
var NUM_ROWS = 4;
for (var i = 0; i < NUM_COLS; i++) {
for (var j = 0; j < NUM_ROWS; j++) {
var tileX = i * 54 + 5;
var tileY = j * 54 + 40;
tiles.push(new Tile(tileX, tileY));
}
}
Aber es ist schon schwer, herauszufinden, ob die Kacheln gut aussehen, weil wir noch keinen Code haben, um sie zu zeichnen. Vielleicht hätten wir das sogar zuerst machen sollen. Manchmal ist es beim Programmieren schwierig, herauszufinden, was man zuerst machen soll, oder? Fügen wir nun dem Objekt
Tile
eine Methode hinzu, welche eine Kachel mit der Oberseite nach unten auf den Canvas zeichnet. Wir zeichnen am zugewiesenen Ort ein abgerundetes Rechteck mit einem niedlichen Khan-Blatt auf der Oberseite.Tile.prototype.draw = function() {
fill(214, 247, 202);
strokeWeight(2);
rect(this.x, this.y, this.size, this.size, 10);
image(getImage("avatars/leaf-green"),
this.x, this.y, this.size, this.size);
};
Nun sind wir soweit und können überprüfen, wie unsere Kacheln aussehen. Fügen wir eine neue
for
-Schleife hinzu, die durch alle Kacheln iteriert und die draw-Methode für sie aufruft.for (var i = 0; i < tiles.length; i++) {
tiles[i].draw();
}
So sieht unser Programm mit diesem Code aus. Versuche, ein bisschen an den verschiedenen Zahlen in der verschachtelten for-Schleife herumzuspielen, und schau dir die Veränderungen am Raster oder an ihrem Aussehen (z.B. ein anderes Logo) an.
Kacheln mit der Oberseite nach oben
Wir haben nun ein Raster von nach unten zeigenden Kacheln. Jetzt können wir ein schwierigeres Problem angehen: jedem ein Bild zuzuweisen, so dass es von jedem Bild im Array zwei gibt, die zufällig verteilt liegen. Es gibt sicherlich viele Möglichkeiten, ich würde aber diese hier vorschlagen:
- Wir legen mithilfe der Funktion
getImage
, die zufällige Bilder aus der Bibliothek holt, ein Array von möglichen Bildern an. - Wir brauchen also für unsere 20 Kacheln nur 10 Bilder. Anschließend legen wir ein neues Array mit 2 Kopien von 10 zufällig ausgewählten Bildern aus dem ersten Array an.
- Wir verteilen die Bilder im Array zufällig, so dass die Paare im Array nicht mehr direkt nebeneinander liegen.
- In der verschachtelten Schleife, in der wir die Kacheln anlegen, weisen wir jeder Kachel ein Bild aus diesem Array zu.
Diese Schritte erscheinen vielleicht noch nicht sinnvoll, aber führen wir sie aus und sehen was passiert.
Schritt 1: Wir legen ein Array von möglichen Bildern an und verwenden dabei die Funktion
getImage
, um sie aus der Bibliothek auszuwählen.var faces = [
getImage("avatars/leafers-seed"),
getImage("avatars/leafers-seedling"),
getImage("avatars/leafers-sapling"),
getImage("avatars/leafers-tree"),
getImage("avatars/leafers-ultimate"),
getImage("avatars/marcimus"),
getImage("avatars/mr-pants"),
getImage("avatars/mr-pink"),
getImage("avatars/old-spice-man"),
getImage("avatars/robot_female_1"),
getImage("avatars/piceratops-tree"),
getImage("avatars/orange-juice-squid")
];
Ich habe ein paar Avatare ausgewählt, aber du kannst dir deine eigenen Lieblingsbilder aussuchen. Am wichtigsten ist es, dass dieses Array mindestens 10 Bilder enthält, damit uns bei den 20 Kacheln die Bilder nicht ausgehen. Wir können aber viel mehr als 10 Bilder hinzufügen, um unser Spiel vielfältiger zu machen, wenn eine neue Runde gestartet wird. Die Liste wird ja erst im nächsten Schritt reduziert.
Schritt 2: Weil wir nur 10 Bilder für die Oberseiten unserer 20 Kacheln brauchen, legen wir ein neues Array an, das 2 Kopien von 10 zufällig ausgewählten Bildern aus dem ersten Array enthält.
Dafür legen wir eine for-Schleife an, die 10 Mal durchläuft. Bei jedem Durchlauf wählen wir zufällig einen Index aus dem Array
faces
für die Oberseiten, übergeben ihn zwei Mal an das Array selected
. Wir benutzen dann die Methode splice, um ihn aus dem Array faces
zu entfernen und ihn nicht zwei Mal auszuwählen. Dieser letzte Schritt ist sehr wichtig!var selected = [];
for (var i = 0; i < 10; i++) {
// Wähle ein Bild zufällig aus dem Array faces
var randomInd = floor(random(faces.length));
var face = faces[randomInd];
// Füge 2 Kopien oben auf dem Array hinzu
selected.push(face);
selected.push(face);
// Entferne das Bild aus dem Array faces damit wir es nicht erneut gewählt wird
faces.splice(randomInd, 1);
}
Schritt 3: Wir verteilen die Bilder im Array "selected" zufällig, damit die Bildpaare dort nicht mehr nebeneinander liegen.
Du hast wahrscheinlich in deinem Leben schon viele Kartenstapel gemischt, aber hast du jemals ein Array in JavaScript gemischt? Die beliebteste Technik für das "Mischen" in irgendeiner Programmiersprache heißt Fisher-Yates Shuffle, und diese werden wir hier verwenden.
Fisher-Yates Shuffle beginnt mit der Auswahl eines zufälligen Elements im Array, und tauscht dieses mit dem letzten Element im Array aus. Im nächsten Schritt wählt sie ein zufälliges Element aus dem Array neben dem letzten Element aus, und tauscht dieses mit dem vorletzten Element aus. Es geht weiter, bis jedes Element getauscht worden ist.
Du kannst dich durch diese Darstellung klicken, um zu sehen, was ich meine:
Um dies in JavaScript zu implementieren, erstellen wir eine Funktion
shuffleArray
, die ein Array nimmt und seine Elemente mischt und das dabei das ursprüngliche Array verändert:var shuffleArray = function(array) {
var counter = array.length;
// Solange es Elemente im Array hat
while (counter > 0) {
// Wähle einen Zufälligen index
var ind = Math.floor(Math.random() * counter);
// Reduziere den Zähler um 1
counter--;
// Und tausche das letzte Element damit
var temp = array[counter];
array[counter] = array[ind];
array[ind] = temp;
}
};
Wenn du den Algorithmus auch nach dem Durcklicken durch die Darstellung und dem Lesen des Codes noch nicht ganz verstehst, versuche es doch einfach mit einem richtigen Kartenstapeloder schau wie watch how Adam Khoury es in dem Youtube Video macht.
Nachdem wir diese Funktion definiert haben, müssen wir sie tatsächlich aufrufen:
shuffleArray(selected);
Und jetzt haben wir ein nach dem Zufall sortiertes Array aus 10 Bildpaaren!
Schritt 4: In der verschachtelten for-Schleife, in der wir die Kacheln anlegen, weisen wir jeder Kachel ein Bild aus diesem Array zu.
Wir haben in unserem Array
selected
nun 20 Bilder. Wir iterieren also 20 Mal, um neue Instanzen von Kacheln an verschiedenen Orten im Gitter zu erzeugen. Um jeder Kachel ein zufälliges Bild zuzuweisen, rufen wir einfach die Methode pop
auf dem Array auf. Diese Methode entfernt das letzte Element aus dem Array und gibt es zurück. Das ist die einfachste Art, um sicherzustellen, dass wir alle Bilder zugewiesen haben, aber keines davon zwei Mal. for (var i = 0; i < NUM_COLS; i++) {
for (var j = 0; j < NUM_ROWS; j++) {
var tileX = i * 54 + 5;
var tileY = j * 54 + 40;
var tileFace = selected.pop();
var tile = new Tile(tileX, tileY, tileFace);
tiles.push(tile);
}
}
Hast du bemerkt, wie dieser Code
tileFace
als dritten Parameter an den Konstruktor von Tile
übergibt? Unser Konstruktor hatte ursprünglich nur 2 Parameter, x
und y
, aber jetzt modifizieren wir ihn so, dass wir auch das Bild der einzelnen Kacheln speichern können. Und ob das Bild nach oben zeigt:var Tile = function(x, y, face) {
this.x = x;
this.y = y;
this.size = 70;
this.face = face;
this.isFaceUp = false;
};
Theoretisch haben wir jetzt also jeder Kachel ein Bild zugewiesen, aber sie werden noch nicht angezeigt. Nun modifizieren wir die Methode
Tile.draw
damit sie Kacheln mit dem Bild nach oben zeichnen kann:Tile.prototype.draw = function() {
fill(214, 247, 202);
strokeWeight(2);
rect(this.x, this.y, this.size, this.size, 10);
if (this.isFaceUp) {
image(this.face, this.x, this.y,
this.size, this.size);
} else {
image(getImage("avatars/leaf-green"),
this.x, this.y, this.size, this.size);
}
};
Um am Ende zu testen, ob alles funktioniert, können wir unsere
for
-Schleife so anpassen, dass sie die Eigenschaft isFaceUp
auf true
setzt bevor gezeichnet wird.for (var i = 0; i < tiles.length; i++) {
tiles[i].isFaceUp = true;
tiles[i].draw();
}
Hier ist das gesamte Programm. Starte es immer wieder neu, um zu sehen, wie sich die Kacheln jedes Mal ändern.
Willst du an der Diskussion teilnehmen?
Noch keine Beiträge.