JavaRush /Blog Java /Random-PL /Sekcja „Gry” w JavaRush: Przydatna teoria

Sekcja „Gry” w JavaRush: Przydatna teoria

Opublikowano w grupie Random-PL
W sekcji „Gry” JavaRush znajdziesz ekscytujące projekty do pisania popularnych gier komputerowych. Chcesz stworzyć własną wersję popularnych gier „2048”, „Sapper”, „Snake” i innych? To proste. Zamieniliśmy pisanie gier w proces krok po kroku. RozdziałAby spróbować swoich sił w roli twórcy gier, nie musisz być zaawansowanym programistą, ale nadal wymagana jest pewna znajomość języka Java. Tutaj znajdziesz informacje, które przydadzą się podczas pisania gier .

1. Dziedziczenie

Praca z silnikiem gry JavaRush wiąże się z wykorzystaniem dziedziczenia. Ale co, jeśli nie wiesz, co to jest? Z jednej strony musisz zrozumieć ten temat: uczy się go na poziomie 11 . Z drugiej strony silnik został celowo zaprojektowany tak, aby był bardzo prosty, aby można było obejść się przy powierzchownej wiedzy na temat dziedziczenia. Czym zatem jest dziedziczenie? Mówiąc najprościej, dziedziczenie to relacja między dwiema klasami. Jeden z nich staje się rodzicem, a drugi dzieckiem (klasą następczą). W takim przypadku klasa nadrzędna może nawet nie wiedzieć, że ma klasy potomne. Te. nie odnosi żadnych szczególnych korzyści z obecności klas dziedziców. Ale dziedziczenie zapewnia wiele korzyści klasie potomków. A najważniejsze jest to, że wszystkie zmienne i metody klasy nadrzędnej pojawiają się w klasie podrzędnej, tak jakby kod klasy nadrzędnej został skopiowany do klasy podrzędnej. Nie jest to do końca prawdą, ale dla uproszczonego zrozumienia dziedziczenia tak. Oto kilka przykładów pozwalających lepiej zrozumieć dziedziczenie. Przykład 1: najprostsze dziedziczenie.
public class Родитель {

}
Klasa Child dziedziczy po klasie Parent za pomocą słowa kluczowego Extends .
public class Потомок extends Родитель {

}
Przykład 2: Używanie zmiennych klasy nadrzędnej.
public class Родитель {

   public int age;
   public String name;
}
Klasa Child może używać zmiennych wieku i imienia klasy Parent tak, jakby były w niej zadeklarowane.
public class Потомок extends Родитель {

   public void printInfo() {

     System.out.println(name+" "+age);
   }
}
Przykład 3: Używanie metod klasy nadrzędnej.
public class Родитель {

   public int age;
   public String name;

   public getName() {
      return name;
   }
}
Klasa Child może używać zmiennych i metod klasy Parent tak, jakby były w niej zadeklarowane. W tym przykładzie używamy metody getName ().
public class Потомок extends Родитель {

   public void printInfo() {

     System.out.println(getName()+" "+age);
   }
}
Tak wygląda klasa Descendant z punktu widzenia kompilatora:
public class Потомок extends Родитель {

   public int age; //  унаследованная переменная
   public String name; //  унаследованная переменная

   public getName() { //  унаследованный метод.
      return name;
  }
   public void printInfo() {

     System.out.println(getName()+" "+age);
   }
}

2. Nadpisanie metody

Czasami zdarzają się sytuacje, że odziedziczyliśmy naszą klasę Descendant od jakiejś bardzo przydatnej klasy Parent, wraz ze wszystkimi zmiennymi i metodami, ale niektóre metody nie działają dokładnie tak, jak byśmy sobie tego życzyli. Albo wcale w sposób, jakiego nie chcemy. Co zrobić w tej sytuacji? Możemy zastąpić metodę, która nam się nie podoba. Robi się to bardzo prosto: w naszej klasie Descendant po prostu deklarujemy metodę z tym samym podpisem (nagłówkiem) co metoda klasy Parent i zapisujemy w niej nasz kod. Przykład 1: Zastępowanie metody.
public class Родитель {

   public String name;

   public void setName (String nameNew) {
       name = nameNew;
  }

   public getName() {
      return name;
  }
}
Metoda printInfo() wypisze frazę „Luke, No!!!”
public class Потомок extends Родитель {

   public void setName (String nameNew) {
       name = nameNew + ",No!!!";
  }

   public void printInfo() {

      setName("Luke");
      System.out.println( getName());
   }
}
Tak wygląda klasa Descendant z punktu widzenia kompilatora:
public Потомок extends Родитель {

   public String name; //  унаследованная переменная

   public void setName (String nameNew) { //  Переопределенный метод взамен унаследованного

       name = nameNew + ", No!!!";
   }
   public getName() { //  унаследованный метод.

      return name;
   }
   public void printInfo() {

     setName("Luke");
     System.out.println(getName());
   }
}
Przykład 2: odrobina magii dziedziczenia (i nadpisywania metod).
public class Родитель {

   public getName() {
      return "Luke";
  }
   public void printInfo() {

     System.out.println(getName());
   }
}
public class Потомок extends Родитель {

   public getName() {
      return "I'm your father, Luke";
  }
}
W tym przykładzie: jeśli metoda printInfo(z klasy Parent) nie zostanie nadpisana w klasie Descendant, to wywołanie tej metody na obiekcie klasy Descendant spowoduje wywołanie jej metody getName(), a nie getName()klasy Parent.
Родитель parent = new Родитель ();
parent.printnInfo();
Kod ten wyświetla na ekranie napis „Łukasz” .
Потомок child = new Потомок ();
child.printnInfo();
W kodzie tym widnieje napis „Jestem twoim ojcem, Łukasz”; .
Tak wygląda klasa Descendant z punktu widzenia kompilatora:
public class Потомок extends Родитель {

   public getName() {
      return "I'm your father, Luke";
   }
   public void printInfo() {

     System.out.println(getName());
   }
}

3. Listy

Jeśli jeszcze nie poznałeś Lists, oto krótki wstęp. Pełne informacje znajdziesz na poziomach 6-7 kursu JavaRush . Listy mają wiele wspólnego z tablicami:
  • może przechowywać wiele danych określonego typu;
  • umożliwiają pobieranie elementów według ich indeksu/numeru;
  • indeksy elementów zaczynają się od 0.
Zalety list: W przeciwieństwie do tablic, listy mogą dynamicznie zmieniać rozmiar. Zaraz po utworzeniu lista ma rozmiar 0. W miarę dodawania elementów do listy jej rozmiar wzrasta. Przykład tworzenia listy:
ArrayList<String> myList = new ArrayList<String>(); // создание нового списка типа ArrayList
Wartość w nawiasach ostrych określa typ danych, jakie może przechowywać lista. Oto kilka metod pracy z listą:
Kod Krótki opis działania kodu
ArrayList<String> list = new ArrayList<String>(); Tworzenie nowej listy ciągów
list.add("name"); Dodaj element na końcu listy
list.add(0, "name"); Dodaj element na początek listy
String name = list.get(5); Pobierz element według jego indeksu
list.set(5, "new name"); Zmień element według jego indeksu
int count = list.size(); Uzyskaj liczbę elementów na liście
list.remove(4); Usuń element z listy
Więcej informacji na temat list można znaleźć w tych artykułach:
  1. Klasa ArrayList
  2. Działająca ArrayList na zdjęciach
  3. Usuwanie elementu z ArrayList

4. Tablice

Co to jest matryca? Macierz to nic innego jak prostokątna tabela, którą można wypełnić danymi. Inaczej mówiąc, jest to tablica dwuwymiarowa. Jak zapewne wiesz, tablice w Javie są obiektami. Standardowy typ tablicy jednowymiarowej intwygląda następująco:
int [] array = {12, 32, 43, 54, 15, 36, 67, 28};
Wyobraźmy sobie to wizualnie:
0 1 2 3 4 5 6 7
12 32 43 54 15 36 67 28
Górna linia wskazuje adresy komórek. Oznacza to, że aby uzyskać liczbę 67, musisz uzyskać dostęp do elementu tablicy o indeksie 6:
int number = array[6];
Tutaj wszystko jest bardzo proste. Tablica dwuwymiarowa to tablica tablic jednowymiarowych. Jeśli słyszysz o tym po raz pierwszy, zatrzymaj się i wyobraź sobie to w swojej głowie. Tablica dwuwymiarowa wygląda mniej więcej tak:
0 Tablica jednowymiarowa Tablica jednowymiarowa
1 Tablica jednowymiarowa
2 Tablica jednowymiarowa
3 Tablica jednowymiarowa
4 Tablica jednowymiarowa
5 Tablica jednowymiarowa
6 Tablica jednowymiarowa
7 Tablica jednowymiarowa
W kodzie:
int [][] matrix = {
{65, 99, 87, 90, 156, 75, 98, 78}, {76, 15, 76, 91, 66, 90, 15, 77}, {65, 96, 17, 25, 36, 75, 54, 78}, {59, 45, 68, 14, 57, 1, 9, 63}, {81, 74, 47, 52, 42, 785, 56, 96}, {66, 74, 58, 16, 98, 140, 55, 77}, {120, 99, 13, 90, 78, 98, 14, 78}, {20, 18, 74, 91, 96, 104, 105, 77} }
0 0 1 2 3 4 5 6 7
65 99 87 90 156 75 98 78
1 0 1 2 3 4 5 6 7
76 15 76 91 66 90 15 77
2 0 1 2 3 4 5 6 7
65 96 17 25 36 75 54 78
3 0 1 2 3 4 5 6 7
59 45 68 14 57 1 9 63
4 0 1 2 3 4 5 6 7
81 74 47 52 42 785 56 96
5 0 1 2 3 4 5 6 7
66 74 58 16 98 140 55 77
6 0 1 2 3 4 5 6 7
120 99 13 90 78 98 14 78
7 0 1 2 3 4 5 6 7
20 18 74 91 96 104 105 77
Aby uzyskać wartość 47, musisz uzyskać dostęp do elementu macierzy w [4] [2].
int number = matrix[4][2];
Jeśli zauważysz, współrzędne macierzy różnią się od klasycznego prostokątnego układu współrzędnych (kartezjańskiego układu współrzędnych). Uzyskując dostęp do macierzy, najpierw określa się y, a następnie x , podczas gdy w matematyce często określa się najpierw x(x, y). Być może zadajesz sobie pytanie: „Dlaczego nie odwrócić matrycy w swojej wyobraźni i uzyskać dostęp do elementów w zwykły sposób poprzez (x, y)? Nie zmieni to zawartości matrycy.” Tak, nic się nie zmieni. Jednak w świecie programowania zwyczajowo nazywa się macierze w formie „najpierw y, potem x”. Należy to przyjąć za oczywistość. Porozmawiajmy teraz o rzutowaniu macierzy na nasz silnik (klasa Game). Jak wiadomo, silnik posiada wiele metod, które zmieniają komórki pola gry przy danych współrzędnych. Na przykład setCellValue(int x, int y, String value). Ustawia określoną komórkę o współrzędnych (x, y) na wartość value. Jak zauważyłeś, metoda ta najpierw przyjmuje dokładnie x, jak w klasycznym układzie współrzędnych. Pozostałe metody silnika działają w podobny sposób. Podczas tworzenia gier często zaistnieje potrzeba odtworzenia stanu matrycy na ekranie. Jak to zrobić? Najpierw w pętli musisz iterować po wszystkich elementach macierzy. Po drugie, dla każdego z nich wywołaj metodę wyświetlania ze współrzędnymi ODWRÓCONYMI. Przykład:
private void drawScene() {
    for (int i = 0; i < matrix.length; i++) {
        for (int j = 0; j < matrix[i].length; j++) {
            setCellValue(j, i, String.valueOf(matrix[i][j]));
        }
    }
}
Naturalnie inwersja działa w dwóch kierunkach. setCellValueMożna przekazać (i, j) do metody , ale jednocześnie pobrać element [j][i] z macierzy. Inwersja może wydawać się nieco trudna, ale warto o tym pamiętać. I zawsze, jeśli pojawią się jakieś problemy, warto wziąć długopisem kartkę papieru, narysować matrycę i odtworzyć, jakie procesy na niej zachodzą.

5. Liczby losowe

Jak pracować z generatorem liczb losowych? Klasa Gamedefiniuje metodę getRandomNumber(int). Pod maską wykorzystuje klasę Randomz pakietu java.util, ale nie zmienia to zasady pracy z generatorem liczb losowych. Jako argument getRandomNumber(int)przyjmuje liczbę całkowitą . Liczba ta będzie górną granicą, którą generator może zwrócić. Dolna granica wynosi 0. Ważny! Generator NIGDY nie zwróci liczby górnej granicy. Na przykład, wywołany getRandomNumber(3)losowo, może zwrócić 0, 1, 2. Jak widać, nie może zwrócić 3. To użycie generatora jest dość proste, ale w wielu przypadkach bardzo skuteczne. Musisz uzyskać losową liczbę w pewnych granicach: Wyobraź sobie, że potrzebujesz trzycyfrowej liczby (100..999). Jak już wiesz, minimalna zwrócona liczba to 0. Będziesz więc musiał dodać do niej 100. Jednak w tym przypadku musisz uważać, aby nie przekroczyć górnego limitu. Aby otrzymać 999 jako maksymalną wartość losową należy wywołać metodę getRandomNumber(int)z argumentem 1000. Pamiętamy jednak o późniejszym dodaniu 100: oznacza to, że górną granicę należy obniżyć o 100. Czyli kod pozwalający uzyskać losowa trzycyfrowa liczba wyglądałaby tak:
int number = 100 + getRandomNumber(900);
Aby jednak uprościć taką procedurę, silnik udostępnia metodę getRandomNumber(int, int), która jako pierwszy argument zwraca minimalną liczbę. Korzystając z tej metody, poprzedni przykład można przepisać:
int number = getRandomNumber(100, 1000);
Liczb losowych można użyć do uzyskania losowego elementu tablicy:
String [] names = {„Andriej”, "Валентин", "Сергей"};
String randomName = names[getRandomNumber(names.length)]
Wywoływanie określonych zdarzeń z określonym prawdopodobieństwem. Poranek człowieka zaczyna się według możliwych scenariuszy: Zaspanie – 50%; Wstawałem punktualnie – 40%; Wstałem godzinę wcześniej niż się spodziewałem – 10%. Wyobraź sobie, że piszesz emulator ludzkiego poranka. Musisz wywołać zdarzenia z określonym prawdopodobieństwem. Aby to zrobić, ponownie musisz użyć generatora liczb losowych. Implementacje mogą być różne, ale najprostsza powinna być zgodna z następującym algorytmem:
  1. ustalamy limity, w ramach których liczba ma zostać wygenerowana;
  2. wygeneruj liczbę losową;
  3. Przetwarzamy wynikową liczbę.
Zatem w tym przypadku limit wyniesie 10. Wywołajmy metodę getRandomNumber(10)i przeanalizujmy, co może nam ona zwrócić. Może zwrócić 10 cyfr (od 0 do 9) i każdą z takim samym prawdopodobieństwem - 10%. Teraz musimy połączyć wszystkie możliwe wyniki i dopasować je do naszych możliwych wydarzeń. Kombinacji może być wiele, w zależności od twojej wyobraźni, ale najbardziej oczywiste brzmią: „Jeśli losowa liczba mieści się w granicach [0..4] - nazwij zdarzenie „Zaspany”, jeśli liczba mieści się w granicach [5..8] ] - „Obudź się” o czasie” i tylko jeśli liczba wynosi 9, to „Wstałem godzinę wcześniej, niż się spodziewałem”. Wszystko jest bardzo proste: w obrębie [0..4] znajduje się 5 liczb, z których każda może zwrócić z prawdopodobieństwem 10%, co w sumie będzie wynosić 50%; w obrębie [5..8] znajdują się 4 liczby, a 9 to jedyna liczba, która pojawia się z prawdopodobieństwem 10%. W kodzie cały ten sprytny projekt wygląda jeszcze prościej:
int randomNumber = getRandomNumber(10);
if (randomNumber < 5) {
    System.out.println("Проспал ");
} else if (randomNumber < 9) {
    System.out.println("Встал вовремя ");
} else {
    System.out.println("Встал на час раньше положенного ");
}
Ogólnie rzecz biorąc, może istnieć wiele opcji wykorzystania liczb losowych. Wszystko zależy tylko od Twojej wyobraźni. Ale są one najskuteczniejsze, jeśli chcesz wielokrotnie uzyskać jakiś wynik. Wtedy wynik ten będzie inny niż poprzedni. Oczywiście z pewnym prawdopodobieństwem. To wszystko! Jeśli chcesz dowiedzieć się więcej o sekcji Gry, oto przydatna dokumentacja, która może Ci pomóc:
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION