Główna zawartość
Programowanie
Kurs: Programowanie > Rozdział 4
Lekcja 5: Gra typu MemoryGra memory: Rysowanie siatki kafelków
Pierwszym krokiem gry w "Pamięć" jest losowe potasowanie płytek, a następnie rozłożenie ich na prostokątnej siatce, obrazkiem do dołu tak, abyśmy nie mogli zobaczyć jaki obrazek jest na drugiej stronie danej płytki.
Płytki wyświetlane obrazkiem do dołu
Aby zacząć programowanie tej gry, zacznijmy od zagadnienia tworzenia płytek, kwestię różnych obrazków na płytkach zostawiając na później.
"Płytka" to wystarczająco ważny obiekt w "Pamięci", aby warto było użyć programowania zorientowanego obiektowo do stworzenia obiektu
Tile
, a następnie utworzenia wielu instancji tego obiektu. Dzięki temu będziemy w stanie określić zarówno właściwości (takie jak położenie czy obrazek), jak i zachowanie (czy obrazek jest widoczny) osobno dla każdego obiektu Tile
.Na początek zdefiniujmy konstruktor obiektu
Tile
. Skoro nie zajmujemy się jeszcze obrazkami, przekażmy jej w argumentach tylko wartości x
oraz y
. Będziemy również zapamiętywać szerokość płytki(stałą) we właściwościach tych obiektów.var Tile = function(x, y) {
this.x = x;
this.y = y;
this.width = 50;
};
Teraz, gdy zdefiniowaliśmy konstruktor, możemy użyć go w pętli do stworzenia płytek w odpowiednich miejscach za pomocą współrzędnych x i y. Właściwie użyjemy dwóch pętli
for
- zagnieżdżonej pętli for
- co ułatwi nam wygenerowanie współrzędnych naszej siatki.Najpierw musimy zadeklarować pustą tablicę
tiles
, aby przechowywać wszystkie te kafelki:var tiles = [];
Nasza zewnętrzna pętla iteruje po liczbie kolumn, którą chcemy, a wewnętrzna pętla iteruje po wierszach - każdy nowy obiekt
Tile
jest tworzony z współrzędnymi x i y odpowiadającym danej kolumnie i wierszowi.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));
}
}
Problem jest to, że ciężko będzie nam ustalić, czy płytki wyglądają dobrze, ponieważ nie mamy żadnego kodu który by je narysował! Właściwie prawdopodobnie powinniśmy od tego zacząć. Czasami podczas programowania ciężko jest zorientować się, od czego zacząć, prawda? Dodajmy więc metodę do obiektu
Tile
, która będzie rysować płytkę obrazkiem do dołu na płótnie. Narysujemy prostokąt z zaokrąglonymi rogami oraz liściem Khana na górze, w wybranym położeniu.Tile.prototype.draw = function() {
fill(214, 247, 202);
strokeWeight(2);
rect(this.x, this.y, this.width, this.width, 10);
image(getImage("avatars/leaf-green"),
this.x, this.y, this.width, this.width);
};
Dopiero teraz możemy wreszcie sprawdzić jak wyglądają nasze płytki! Dodajmy nową pętlę
for
która iteruje po wszystkich płytkach i wywołuje na każdej z nich metodę rysującą:for (var i = 0; i < tiles.length; i++) {
tiles[i].draw();
}
Oto jak prezentuje się cały nasz program, zawierający ten kod. Spróbujcie zmieniać różne cyfry w pętlach aby sprawdzić, jak wpływają one na wygląd siatki. Możecie również zmienić sposób, w jaki rysowane są płytki(na przykład zmienić logo)
Płytki z widocznym obrazkiem
Teraz, gdy mamy już siatkę płytek, zajmijmy się trudniejszą kwestią: przypisaniem każdej płytce obrazka tak, aby każdy obrazek z tablicy był losowo przypisany do dwóch różnych płytek. Istnieje wiele sposobów na osiągnięcie tego, ale oto moja propozycja:
- Tworzymy tablicę dostępnych obrazków, używając funkcji
getImage
aby wybrać je z naszej biblioteki. - Będziemy potrzebować tylko 10 obrazków na 20 płytek, tak więc stwórzmy tablicę która będzie przechowywać dwie kopie 10 losowo wybranych obrazków z poprzedniej tablicy.
- Ustawiamy kolejność obrazków w nowej tablicy w sposób losowy, tak aby te same obrazki nie były już obok siebie.
- W zagnieżdżonej pętli for tworzymy płytki, przypisując obrazek z tablicy do każdej płytki.
Te kroki mogą jeszcze w tym momencie wydawać się bezsensowne - zróbmy każdy z nich i zobaczmy, co uda nam się uzyskać.
Krok 1: Tworzymy tablicę dostępnych obrazków, używając funkcji
getImage
aby wybrać je z naszej biblioteki: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")
];
Wybrałem kilka avatarów, ale Wy możecie wybrać te obrazki, które wam się najbardziej podobają. Istotną kwestią jest upewnienie się, że tablica ma przynajmniej 10 obrazków, dzięki czemu nie zabraknie ich dla naszych 20 płytek. Możemy dodać znacznie więcej niż 10 obrazków, dzięki czemu gra będzie bardziej różnorodna przy kolejnych uruchomieniach, ponieważ lista obrazków zostanie zawężona w następnym kroku.
Krok 2: Będziemy potrzebować tylko 10 obrazków na 20 płytek, tak więc stwórzmy tablicę która będzie przechowywać dwie kopie 10 losowo wybranych obrazków z poprzedniej tablicy.
Aby to osiągnąć, stwórzmy pętlę for która będzie wykonywać dziesięć iteracji. W każdej z nich będziemy w sposób losowy wybierać indeks z tablicy
faces
, dodawać dwie kopie wybranego obrazka do tablicy selected
, a następnie używać metody splice aby usunąć wylosowany obrazek z tablicy faces
, aby nie wylosować danego obrazka dwa razy. Ostatni krok jest bardzo ważny!var selected = [];
for (var i = 0; i < 10; i++) {
// Wybierzmy losowy obrazek z tablicy obrazków
var randomInd = floor(random(faces.length));
var face = faces[randomInd];
// Dodajmy dwie kopie do nowej tablicy
selected.push(face);
selected.push(face);
// Usuńmy z pierwszej tablicy, aby obrazek nie został wybrany ponownie
faces.splice(randomInd, 1);
}
Krok 3: Ustawiamy kolejność obrazków w nowej tablicy w sposób losowy, tak aby te same obrazki nie były już obok siebie.
Prawdopodobnie tasowałeś talię kart w swoim życiu, ale czy kiedykolwiek tasowałeś tablicę w JavaScript? Najpopularniejszą techniką tasowania w dowolnym języku programowania jest Tasowanie metodą Fisher-Yates i z tego tutaj skorzystamy.
Tasowanie metodą Fisher-Yatesa zaczyna się od wybrania losowego elementu w tablicy i zamiana miejscami z ostatnim elementem tablicy. W następnym kroku wybieramy losowy element z wyjątkiem ostatniego elementu i zamieniamy go z przedostatnim elementem. Całość się powtarza, aż zamienimy miejscami wszystkie elementy.
Możesz przejść przez tą wizualizację, aby zobaczyć jak to działa:
Aby zaimplementować to w JavaScript, stwórzmy funkcję
shuffleArray
, która przyjmuje tablicę jako parametr i tasuje jej elementy zmieniając oryginalna tablicę:var shuffleArray = function(array) {
var counter = array.length;
// Gdy istnieją elementy w tablicy
while (counter > 0) {
// Wybierz losowy indeks
var ind = Math.floor(Math.random() * counter);
// Zmniejsz licznik o 1
counter--;
// Zamień ostatni element z nim
var temp = array[counter];
array[counter] = array[ind];
array[ind] = temp;
}
};
Jeśli ten algorytm jeszcze nie jest dla ciebie zbyt jasny po wizualizacji i przeczytaniu kodu, możesz spróbować z prawdziwą talią kart lub zobaczyć jak Adam Khoury przedstawia działanie w jego filmie na YouTube.
Po zdefiniowaniu funkcji musimy ją wywołać:
shuffleArray(selected);
I tak oto otrzymaliśmy tablicę dziesięciu par, potasowanych losowo!
Krok 4: W zagnieżdżonej pętli for tworzymy płytki, przypisując obrazek z tablicy do każdej płytki.
Mamy 20 obrazków w naszej tablicy
selected
i iterujemy 20 razy aby stworzyć nowe płytki w odpowiednich miejscach siatki. Aby przypisać każdy obrazek do płytki, możemy użyć metody pop
dostarczanej przez tablicę. Metoda ta usuwa ostatni element z tablicy jednocześnie zwracając go. Jest to najłatwiejsza metoda na przypisanie wszystkich obrazków bez duplikatów.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);
}
}
Zauważ, że nasz kod przekazuje
tileace
jako trzeci parametr do konstruktora Tile
? Nasz konstruktor początkowo posiadał 2 parametry, x
iy
, ale zmodyfikujemy go teraz, aby pamiętał też obrazek każdej płytki oraz to. czy płytka leży obrazkiem do góry:var Tile = function(x, y, face) {
this.x = x;
this.y = y;
this.width = 70;
this.face = face;
this.isFaceUp = false;
};
Więc teoretycznie mamy teraz obrazki przypisane do każdego pola, ale jeszcze ich nie wyświetlamy! Zmodyfikujmy metodę
Tile.draw
, aby mogła narysować odkryte płytki:Tile.prototype.draw = function() {
fill(214, 247, 202);
strokeWeight(2);
rect(this.x, this.y, this.width, this.width, 10);
if (this.isFaceUp) {
image(this.face, this.x, this.y,
this.width, this.width);
} else {
image(getImage("avatars/leaf-green"),
this.x, this.y, this.width, this.width);
}
};
Teraz, aby przetestować działanie metody, możemy zmienić działanie naszej pętli
for
tak, aby zmieniała wartość zmiennej isFaceUp
na true
przed wylosowaniem kolejnej płytki:for (var i = 0; i < tiles.length; i++) {
tiles[i].isFaceUp = true;
tiles[i].draw();
}
Oto gotowy program. Spróbujcie go parę razy uruchomić od nowa, aby zobaczyć jak zmieniają się płytki.
Chcesz dołączyć do dyskusji?
Na razie brak głosów w dyskusji