Fő tartalom
Programozás
Tantárgy/kurzus: Programozás > 4. témakör
5. lecke: Memóriajáték készítéseMemóriajáték: Rács rajzolása
A „Memória” játék első lépése, hogy véletlenszerűen összekeverjük a kártyákat, és fejjel lefele elrendezzük egy téglalap alakú rácson, így nem láthatjuk a képeket a kártyák másik oldalán.
Lefordított kártyák
Kezdjük azzal a játék programozását, hogy lefele fordított kártyákat készítünk. Majd később találjuk ki, hogyan csináljuk meg a különböző képeket.
A „kártya” van annyira fontos objektum a „Memória” játékban, hogy objektum-orientált elveket használva definiáljunk egy
Tile
objektumot, és aztán létrehozzuk ennek több példányát. Így képesek leszünk minden egyes kártyánál (Tile
) összekapcsolni a tulajdonságokat (például a helyzetet, és a képet), és a viselkedést is (például a megrajzolást).Kezdjük azzal, hogy definiáljuk a
Tile
konstruktor függvényt. Mivel a képpel most még nem foglalkozunk, csak az x
és y
argumentumokat adjuk át. Ne felejtsük el a kártya (konstans) méretét (size) sem, ami szintén az objektum tulajdonsága.var Tile = function(x, y) {
this.x = x;
this.y = y;
this.size = 50;
};
Most, hogy definiáltuk a konstruktort, használhatjuk ezt egy ciklusban, amelyben elkészítjük a kártyákat a megfelelő x és y helyzetekben. Valójában két
for
ciklust fogunk használni – egymásba ágyazott for
ciklusokat – ami alapjaiban leegyszerűsíti a rács koordinátáinak generálását. Először deklarálunk egy üres
tiles
tömböt, amiben eltároljuk az összes kártyát:var tiles = [];
A külső ciklusunk addig iterál, amennyi oszlopot csak szeretnénk, a belső ciklusunk pedig addig, amennyi sort, és minden új
Tile
-t inicializálunk azzal az x és y értékkel, ami a neki megfelelő sort illetve oszlopot határozza meg.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));
}
}
De, jajj, nehéz eldönteni, hogy a kártyák jók-e, mert nincs még kódunk, ami megrajzolja őket! Valójában ezzel kellett volna kezdenünk. A programozásban néha nehéz előre kitalálni, hogy mivel is kezdjük, ugye? Adjunk hozzá egy metódust a
Tile
objektumhoz, amely megrajzolja a kártyát a vásznon lefordítva. Egy lekerekített téglalapot fogunk rajzolni a megadott helyre egy jópofa Khan levéllel a tetején.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);
};
Most már nagyon közel vagyunk ahhoz, hogy meg tudjuk nézni, hogy festenek a kártyáink! Adjunk hozzá egy új
for
ciklust, ami végigjárja az összes kártyát és meghívja a kirajzoló metódusukat:for (var i = 0; i < tiles.length; i++) {
tiles[i].draw();
}
Így fest a programunk az eddigi kódokkal. Módosítgasd a számokat az egymásba ágyazott for ciklusban, hogy lásd, hogyan változik a rács, vagy változtasd meg a kirajzolást (például egy másik logót használva)!
Felfordított kártyák
Most, hogy elkészültünk a lefordított kártyákkal, foglalkozzunk kicsit a trükkösebb feladattal: rendeljünk mindegyik kártyához egy képet úgy, hogy minden képből kettő szerepeljen a tömbben, véletlenszerűen elosztva. Sokféle módon meg tudnánk ezt oldani, de a következőt javasoljuk:
- Készítsünk egy tömböt a lehetséges képekkel, a képeket a függvénykönyvtárunkból kiválasztva a
getImage
függvény segítségével. - Csak 10 képre lesz szükségünk a 20 kártyához, ezért készítsünk egy új tömböt, amely az első tömbben tárolt véletlenszerűen kiválasztott 10 kép 2-2 másolatát tárolja
- Keverjük össze a kiválasztott képek tömbjét, így a képpárok már nem egymás mellett fognak elhelyezkedni a tömbben.
- Az egymásba ágyazott for ciklusban, ahol a kártyákat elkészítjük, rendeljük hozzá a képeket a tömbből minden egyes kártyához.
Lehet, hogy ezek a lépések még nem érthetőek – készítsük elő mindent egyesével, és nézzük meg, hogy néznek ki.
1. lépés: Készítsünk egy tömböt a lehetséges képekkel, a képeket a függvénykönyvtárunkból kiválasztva a
getImage
függvény segítségével: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")
];
Csomó avatárt választottunk ki, de nyugodtan megváltoztathatod a képeket a kedvenc képeidre. Az fontos, hogy figyelj arra, hogy legalább 10 kép legyen a tömbben, így lesz elég kép a 20 kártyánkhoz. Akár 10-nél sokkal több képet is hozzáadhatunk, így a játékunk változatosabb lesz, mert a tömböt csak a következő lépésben fogjuk leszűkíteni.
2. lépés: Csak 10 képre lesz szükségünk a 20 kártyához, ezért készítsünk egy új tömböt, ami az első tömbben tárolt véletlenszerűen kiválasztott 10 kép 2-2 másolatát tárolja.
Ahhoz, hogy ezt megcsináljuk, készítünk egy for ciklust, ami 10 alkalommal iterál. Minden iterációban véletlenszerűen kiválasztunk egy indexet a
faces
tömbből, ezt kétszer elhelyezzük a selected
tömbben, majd a splice metódust használva eltávolítjuk a faces
tömbből, hogy ne válasszuk ki már többször. Az utolsó lépés nagyon fontos!var selected = [];
for (var i = 0; i < 10; i++) {
// Randomly pick one from the array of faces
var randomInd = floor(random(faces.length));
var face = faces[randomInd];
// Push 2 copies onto array
selected.push(face);
selected.push(face);
// Remove from faces array so we don't re-pick
faces.splice(randomInd, 1);
}
3. lépés: Keverjük össze a kiválasztott képek tömbjét, így a képpárok már nem egymás mellett fognak elhelyezkedni a tömbben.
Valószínűleg már kevertél kártyapaklit a való életben kártyapaklit, de vajon tömböt kevertél már JavaScriptben? A legnépszerűbb keverési technika bármelyik programozási nyelvben a Fisher-Yates keverés, amit mi is használni fogunk.
A Fisher-Yates keverés úgy kezdődik, hogy elsőként kiválasztunk egy véletlen elemet bárhol a tömbből, és kicseréljük ezt a tömb utolsó elemével. A következő lépésben kiválasztunk egy véletlen elemet bárhol a tömbből az utolsó elemet kivéve, és kicseréljük ezt az utolsó előtti elemmel. Ezt így folytatjuk addig, amíg minden elemet ki nem cseréltünk.
Végigkattintgathatod az alábbi vizualizációt, hogy megértsd mire gondolok:
Ahhoz, hogy ezt JavaScriptben implementáljuk, készítsünk egy
shuffleArray
függvényt, ami vesz egy tömböt, és összekeveri az elemeit, megváltoztatva az eredeti tömböt:var shuffleArray = function(array) {
var counter = array.length;
// While there are elements in the array
while (counter > 0) {
// Pick a random index
var ind = Math.floor(Math.random() * counter);
// Decrease counter by 1
counter--;
// And swap the last element with it
var temp = array[counter];
array[counter] = array[ind];
array[ind] = temp;
}
};
Ha az algoritmus nem érthető teljesen a vizualizáción végighaladva és a kódot elolvasva, próbáld ki egy valódi kártyapaklival vagy nézd meg hogyan csinálja Adam Khoury ebben a Youtube videóban.
Most, hogy már definiáltuk a függvényt, meg is hívjuk:
shuffleArray(selected);
És már van is egy tömbünk 10 pár képpel, véletlenszerűen összekeverve!
4. lépés: Az egymásba ágyazott for ciklusban, ahol a kártyákat elkészítjük, rendeljük hozzá a képeket a tömbből minden egyes kártyához.
A
selected
tömbünkben van 20 képünk, és végigiterálunk 20 alkalommal, hogy példányosítsuk a kártyáinkat a rács adott pontjaiban. Egy véletlen kép kiválasztásához minden kártyánál meghívhatjuk a pop
metódust a tömbre. Ez a metódus eltávolítja az utolsó elemet a tömbből és visszatér vele, és ez a legegyszerűbb módja annak, hogy biztosan minden egyes képet hozzárendeljünk valamelyik kártyához, és ne is legyen dupla hozzárendelés. 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);
}
}
Észrevetted, hogyan adja át a kód a
tileFace
-t a Tile
konstruktor harmadik paramétereként? A konstruktorunknak eredetileg 2 paramétere volt, x
és y
, de most módosítjuk, hogy el tudjuk tárolni a képet minden egyes kártyához, illetve azt is eltároljuk, hogy felfordított állapotban van-e:var Tile = function(x, y, face) {
this.x = x;
this.y = y;
this.size = 70;
this.face = face;
this.isFaceUp = false;
};
Most már elméletileg minden képet hozzárendeltük valamelyik kártyához, de még nem rajzoltuk ki őket! Módosítsuk a
Tile.draw
metódust úgy, hogy kirajzolja a kártyákat, amik felfordított állapotúak: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);
}
};
Végül tesztelhetjük, hogy működik-e, azzal, hogy megváltoztatjuk a
for
ciklusunkat úgy, hogy minden isFaceUp
tulajdonságot true
értékre állítunk a kirajzolás előtt:for (var i = 0; i < tiles.length; i++) {
tiles[i].isFaceUp = true;
tiles[i].draw();
}
És itt van az egész együtt. Kezdd újra, hogy lásd, hogyan változnak a kártyák minden alkalommal!
Szeretnél részt venni a beszélgetésben?
Még nincs hozzászólás.