W tym zadaniu utworzymy prostą grę, w której będziemy rzucać piłką do kosza. W grze tej będzie można podać swój nick, jak również zdobywać punkty. Po trzech niecelnych rzutach będzie wyświetlał się napis “Game Over”. Dodatkowo wynik każdej gry będzie można zapisać do pliku. Główny ekran gry będzie wyglądał tak jak poniżej.
Tworzenie projektu
Zacznijmy od utworzenia projektu w oprogramowaniu Greenfoot. W tym celu kliknij w menu głównym na “Scenario -> New”, a następnie utwórz projekt o nazwie kosz.
Przygotuj sobie obrazek, który będzie przedstawiał boisko do gry w koszykówkę a następnie utwórz klasę o nazwie Boisko klikając prawym przyciskiem myszy na klasę World -> New Subclass tak jak na rysunku poniżej.
Po dodaniu klasy Boisko warto będzie uruchomić nasz program. Program uruchamiamy poprzez kliknięcie na przycisk Compile w prawym dolnym rogu środowiska Greenfoot, a następnie klikamy na przycisk Run. Po uruchomieniu programu powinien pojawić się rysunek boiska do koszykówki.
Następnie musimy zdefiniować czego będziemy potrzebowali do naszej gry. Będą to:
- Kosz do gry
- Gracz
- Piłka do koszykówki
- Napis Game Over
- Uzytkownik do wyswietlania wyniku gracza
Stwórzmy więc w naszym programie następujących aktorów oraz przypiszmy im obrazki:
- Pilka - rodzic dla PilkaNiecelna oraz PilkaOK
- PilkaNiecelna - klasa piłki, której instancja (reprezentacja) jest tworzona podczas błędnego rzutu,
- PilkaOK - klasa piłki, której instancja (reprezentacja) jest tworzona podczas poprawnego rzutu,
- Gracz - klasa reprezentująca koszykarza,
- GameOver - klasa wyświetlająca napis GAME OVER po trzech błędnych rzutach do kosza,
- Uzytkownik - klasa reprezentująca użytkownika, zawierająca nickname oraz wynik punktowy gracza,
- Kosz - klasa reprezentująca kosz
- Pasek - klasa reprezentująca dolny pasek postępu z mocą rzutu piłki.
Struktura klas będzie przedstawiała się następująco:
Tworzenie boiska
Zacznijmy od zaprogramowania boiska do gry w koszykówkę. Chcielibyśmy aby boisko zmieniało rozmiar w zależności od rozdzielczości naszego monitora. W tym celu musimy automatycznie przeliczyć długość i wysokość ekranu. Użyjemy do tego klasy Toolkit znajdującej się w pakiecie Javy o nazwie java.awt. W tym celu zaimportujmy wszystkie klasy znajdujące się w pakiecie java.awt tak jak na rysunku poniżej.
W klasie Boisko pobierzmy szerokość oraz wysokość ekranu, a następnie dodajmy wszystkich aktorów, których będziemy potrzebowali do utworzenia naszej gry.
Na powyższym zrzucie ekranu widać przykład kodu, w którym zostaje pobrana zarówno szerokość jak i wysokość ekranu (linie 15-16). Zmienne te zostały zapisane do statycznych zmiennych. Zmienna statyczna to taka zmienna, do której programista ma dostęp z poziomu klasy. Do zdefiniowania takiej zmiennej używa się słowa kluczowego static.
Należy teraz zdefiniować konstruktor dla klasy Boisko, który przeskaluje nam rozmiar boiska jak również rozmieści obiekty na boisku.
Na powyższym rysunku w linii 29 widać przeskalowanie tła jako dwie trzecie obecnego rozmiaru ekranu. W liniach 33-36 widać inicjalizację obiektów. Kod znajdujący się w liniach 37-40 dodaje obiekty do boiska w odpowiednich lokalizacjach. Taka konfiguracja pozwala na to, że nasze boisko będzie wyglądało bardzo podobnie w przypadku różnych rozdzielczości ekranów.
Zapewne jesteś zainteresowany/na częścią kodu znajdującą się w linii 30. W linii 30-stej używamy klasy z pakietu Javy javax.swing o nazwie JOptionPane. Klasa ta jest odpowiedzialna za wyświetlenie okienka, w którym gracz poda nazwę swojego użytkownika, i która później będzie użyta do zapisania wyniku gracza. Aby użyć klasę JOptionPane należy zaimportować pakiet swinga, tak jak na rysunku poniżej (linia 3).
Dodajmy do klasy Boisko jeszcze jedną metodę, która przyda się nam w przyszłości. Będzie to metoda getUzytkownik, którą widzimy obok. Będzie ona pobierać obiekt typu Uzytkownik.
Warto jest teraz uruchomić ponownie program w celu zobaczenia czy wszystkie obiekty zostały wyświetlone na płycie boiska. Uwaga! Staraj się kompilować program jak najczęściej. Zobaczysz wtedy rezultaty swojej pracy jak również wcześniej wyłapiesz potencjalne błędy.
Dodanie paska mocy rzutu
Chcielibyśmy dodać pasek postępu od którego uzależniony będzie wynik rzutu. Rozpoczęcie rzutu będzie wywołane poprzez wciśnięcie klawisza spacji. Gdy pasek postępu zaświeci się na zielono rzut będzie udany. Natomiast rzut w przypadku paska w kolorze czerwonym będzie oznaczał rzut niecelny.
Jeżeli jeszcze nie utworzyłeś/aś klasy Pasek dziedziczącej z klasy Actor to jest to miejsce, w którym należy ją utworzyć, gdyż zajmiemy się zaprogramowaniem paska rzutu. W klasie Pasek będziemy używać klasy Color z pakietu awt,więc należy dodać do niej stosowną klauzulę import (linia 2 z poniższego rysunku).
W klasie Pasek musimy zdefiniować kilka parametrów, a mianowicie:
- szerokosc - długość paska mocy rzutu,
- wysokosc - wysokość paska,
- max - maksymalną wartość paska,
- pozycja - aktualną wartość paska,
- pixelPrzezPozycje - przeliczenie ilości pixeli na aktualną wartość paska.
Wszystkie pola klasy powinny być typu int, tak jak na poniższym przykładzie kodu:
Do klasy Pasek dodajmy metodę, która aktualizuje status paska rzutu i wypełnia go odpowiednimi kolorami. W tym celu będziemy musieli napisać kod taki, jak na poniższym obrazku.
Metoda update nie zwraca żadnej wartości. Jej zadaniem jest tylko aktualizacja paska mocy rzutu oraz jego kolorowanie. W tym celu ustawiamy obrazek przy pomocy metody setImage (linia 31). W linii 32 pobieramy wcześniej ustawiony obrazek i zapisujemy go do zmiennej bar. Następnie ustawiamy kolor wypełnienia na biały (inia 33) i rysujemy prostokąt o wymiarach wcześniej zdefiniowanych (linia 34).
Chcielibyśmy aby koszykarz pudłował rzut gdy nasz pasek mocy rzutu świeci się na czerwono i trafiał do kosza gdy pasek jest koloru zielonego. W tym celu należy odpowiednio pokolorować pasek, tak jak ma to miejsce w liniach 35-41.
Do naszej klasy musimy dodać jeszcze funkcjonalność resetu stanu paska. Żeby pasek był cały czas w ruchu, musimy dodać widoczną obok metodę zresetujPasek, która resetuje pozycję paska.
Warto również dodać metodę, która będzie pobierała aktualną pozycję paska rzutu, gdyż może nam się przydać w przyszłości. Dodajmy więc następujące 3 linie kodu do naszej klasy Pasek.
Pozostaje nam jeszcze oprogramowanie zachowania naszego paska. W tym celu musimy uzupełnić ciało metody act() (jest ona domyślnie utworzona i możemy ją zobaczyć zaraz po tym, jak otworzymy do edycji każdą klasę) tak, jak na poniższym przykładzie kodu.
W powyższym kodzie widać wywołanie dwóch wcześniej napisanych metod. Metoda update() uzupełnia pasek stanu rzutu do końca. Wyrażenie pozycja++ znaczy nic innego jak podniesienie wartości zmiennej o wartość 1 i jest odpowiednikiem wyrażenia pozycja=pozycja+1. Metoda zresetujPasek() sprawdza czy pozycja paska jest równa 100 i w zależności od rezultatu tego sprawdzenia resetuje stan paska.
Dobrze jest teraz uruchomić program aby sprawdzić czy pasek postępu jest widoczny na ekranie.
Klasa użytkownika
W celu zapisywania wyników gracza wyedytujmy wcześniej stworzoną klasę Uzytkownik. Klasa powinna zawierać 3 pola, a mianowicie:
- nickname - pole typu String przechowujące nick gracza,
- wynik - pole typu int przechowujące wynik gracza,
- licznikBledow - licznik typu int zliczający błędne rzuty gracza.
Do klasy dodajmy metody, które inkrementują (zwiększają o jeden) wartości zmiennych wynik oraz licznikBledow, jak również metody pobierające wszystkie powyższe zmienne. Kod takich metod będzie następujący:
Do utworzenia instancji Uzytkownika przyda nam się również konstruktor z jednym argumentem przyjmującym nick gracza, jak również wyświetlenie wyniku gracza na górze ekranu. Przykład kodu napisu idetyfikującego gracza i wyświetlającego jego wynik punktowy jak również kodu konstruktora znajduje się na rysunku poniżej.
Zachowanie piłki
Jeżeli jeszcze nie masz utworzonej struktury klas takiej, jak na rysunku obok - zrób to. Dla piłki zostaną utworzone trzy klasy. Klasa Pilka, gdzie umieścimy logikę lotu piłki oraz klasa PilkaNiecelna, reprezentująca błędny rzut do kosza, a także klasa PilkaOK, która zostanie użyta w przypadku rzutu podczas którego pasek postępu jest pokolorowany na zielono. Struktura klas znajdująca się na rysunku obok, w językach programowania nazywa się dziedziczeniem. Znaczy to tyle, że klasa Pilka jest rodzicem dla klasy PilkaNiecelna oraz PilkaOK. Dzięki mechanizmowi dziedziczenia, klasa oprócz swoich pól i metod posiada również dostęp do pól i metod swojego rodzica. Pamiętaj, że w języku programowania Java, można dziedziczyć tylko po jednej klasie (nie ma tutaj wielodziedziczenia - czyli klasa PilkaNiecelna nie może dziedziczyć po klasie Pilka i równocześnie, np. po klasie Uzytkownik).
Do klasy Pilka dodajmy metodę, która będzie miała za zadanie skierować piłkę w stronę kosza (rzut poprawny). Metoda ta będzie wyglądała następująco:
W linii 30 na powyższym rysunku widać pobranie obiektu Kosz ze środowiska. Składowe linii 30 opisane są poniżej:
- Kosz kosz - deklaracja zmiennej
- getWorld() - pobranie wszystkiego ze środowiska
- getObjects(Kosz.class) - pobranie wszystkich obiektów klasy Kosz
- get(0) - pobranie pierwszego znalezionego obiektu
- (Kosz) - zadeklarowanie programowi, że zwrócony obiekt będzie typu Kosz
Metoda czyPunkt() ukazana poniżej zwraca wartość true jeżeli piłka znajdzie się w odległości 60 pikseli od środka rysunku, a następnie usuwa obiekt ze świata (linie 91-95). Piłka zostanie usunięta również wtedy,kiedy zawodnik nie trafi do kosza i piłka dotknie końcowej krawędzi boiska (linie 96-99).
Pewnie ciekawi Cię co znaczy wyrażenie Math.abs w linii 91 na powyższym fragmencie kodu. Math jest to biblioteka z wbudowanymi funkcjami matematycznymi. Funkcja abs jest to natomiast wartość bezwzględna.
Game Over
Przejdźmy teraz do implementacji klasy GameOver[a][b]. Klasa ta będzie posiadać tylko jedną metodę również o nazwie pokazGameOver(), tak jak na rysunku poniżej:
W linii 22 tworzymy czerwony napis GAME OVER na czarnym tle oraz zatrzymujemy naszą aplikację poprzez użycie metody Greenfoot.stop().
Koszykarz
Koszykarz w naszej grze powinien reagować na klawisz spacji. W reakcji na jej wciśnięcie piłka ma zostać rzucona do kosza. W tym celu napiszmy metodę do klasy Gracz, która będzie dodawała odpowiedni typ piłki, w zależności od paska mocy rzutu, widocznego na dole ekranu naszej gry.
Na powyższym obrazku widać, że jeżeli pasek postępu znajduje się pomiędzy wartościami 35 a 65 wtedy rzut będzie poprawny i zostanie utworzona piłka PilkaOK()(czyli taka, która wpada do kosza). W przeciwnym przypadku nie trafimy do kosza.
Przydadzą się nam jeszcze metody, które sprawdzą czy obiekt klasy Pilka znajduje się w świecie, jak również metoda, która wyświetli nam napis GAME OVER po trzech błędnych rzutach do kosza. Przykład obu metod znajduje się poniżej.
Pozostaje nam jeszcze napisanie metody act(), w której umieścimy wcześniej napisane metody.
W klasie Kosz nie ma żadnej implementacji. Służy nam ona tylko do wyświetlenia kosza.
To już wszystko biorąc pod uwagę logikę naszej gry. Pamiętaj, że zawsze możesz dowolnie bawić się i modyfikować grę.
W następnych rozdziałach zostały opisane zaawansowane zagadnienia dla chętnych takie jak obliczenia fizyczne do animacji lotu piłki jak również operacje na plikach do zapisu wyników.
Zaawansowane obliczenia fizyczne
Przejdźmy do edycji klasy Pilka. Dodajmy do klasy Pilka zmienne, tak jak na poniższym rysunku.
Powyższe zmienne są używane w rzucie ukośnym i mają następujące znaczenie:
- v0 - predkosc poczatkowa pilki,
- sx - przemieszczenie wzgledem osi x,
- sy - przemieszczenie wzgledem osi y,
- v1 - wektor prędkości względem osi y,
- v2 - wektor prędkości względem osi x,
- angle - kąt nachylenia podany w radianach,
- g - przyspieszenie ziemskie
- t,t1 - czas
- direction - kierunek lotu pilki góra/dół[c][d]
- counter - zmienna pomocnicza
- hmax - maksymalna wysokość na którą może wznieść się piłka
W celu rozjaśnienia poszczególnych zmiennych warto spojrzeć na poniższy schemat rzutu ukośnego.
Następnie zaimplementujmy (umieśćmy w kodzie) wzory rzutu ukośnego:
- przemieszczenie
- funkcje trygonometryczne
- maksymalna wysokość
Ich implementacja znajduje się na rysunku poniżej:
W powyższej implementacji korzystamy z klasy Math pochodzącej z biblioteki java.lang. Klasa Math zawiera szereg przydatnych funkcji matematycznych.
Pozostaje nam jeszcze do napisania funkcja, która będzie odpowiadała za animację naszej piłki do kosza. Przykładowy kod funkcji znajduje się poniżej.
W linii 72 pobieramy aktualny czas w sekundach w celu obliczenia różnicy czasowej od momentu naciśnięcia spacji. Jeżeli piłka dotknie wysokości ekranu, wtedy zmieniamy jej lot.
Do naszej aplikacji przyda się również metoda do ustawiania kąta rzutu znajdująca się na rysunku obok. Zwróć uwagę na to, że miara kąta rzutu powinna być podana w radianach.
W klasie PilkaNiecelna oraz PilkaOK dodajemy wywołanie metod zaprezentowanych powyżej. W konstruktorze piłek dodaj wywołanie metody ustawMiare(), natomiast w metodach act() obu klas dodaj wywołanie metod animutLotPilki() i czyPunkt().
Nie bój się manipulować wartościami parametrów. Gdy program nie działa tak jak powinien umiejętnie manipuluj wartościami kątów czy też pozostałych parametrów.
Klasa Stats
Klasa Stats będzie odpowiedzialna za operacje na plikach, listach oraz mapach. Dzięki niej będziemy mogli przechowywać ilość punktów zdobytych przez każdego zawodnika.
Z menu Greenfoot’a wybieramy Edit -> New Class (Ctrl + N) W nowym oknie wpisujemy nazwę klasy i klikamy Ok
W naszej klasie Stats automatycznie zostanie wygenerowany konstruktor oraz przykładowa metoda:
Usuwamy automatycznie wygenerowany konstruktor oraz metodę i dodajemy nasz własny kod:
W naszym kodzie widzimy następujące elementy:
1. Zmienna instance – ta zmienna będzie przechowywać obiekt klasy Stats. Opis innych zmiennych w rozdziale niżej.
2. Konstruktor jest prywatny i dostęp do niego jest możliwy tylko z wnętrza klasy Stats.
3. Metoda getInstance() – utworzy obiekt klasy Stats, jeśli wcześniej nie został utworzony.
Wzorzec projektowy Singleton:
Singleton – wzorzec projektowy, który ogranicza możliwość tworzenia wielu obiektów/instancji danej klasy, oraz zapewnia dostęp do utworzonego obiektu.
4. W konstruktorze znajduje się wywołanie metody readsStatsFromFile(filename) – ta metoda będzie odpowiedzialna za wczytanie z pliku punktacji wszystkich graczy. Dla potrzeb naszej gry załóżmy że format pliku w którym będziemy trzymać nasze statystyki będzie wyglądał w następujący sposób:
W pierwszej linii zawsze będzie informacja o tym, który gracz zdobył najwięcej punktów, oraz jego wynik. W pozostałych liniach umieszczone zostaną nazwy użytkowników oraz wyniki.
Czytanie z pliku
Metoda readsStatsFromFile pozwala na odczytywanie danych z pliku i jest częścią klasy Stats. Nazwę pliku, jaki zostanie utworzony podajemy jako parametr metody i będzie to zmienna fileName którą deklarujemy w ciele klasy Stats. Użyjemy również dodatkowej zmiennej, która będzie wskazywać miejsce, w którym plik będzie się znajdował (zmienna filePath):
System.getProperty(”user.home”); zwróci lokalizację katalogu domowego użytkownika systemu.
Objaśnienie metody readStatsFromFile:
Na początku metody zadeklarowane są zmienne, które zostaną wykorzystane do odczytu pliku. Trzy najważniejsze zmienne to br, fileReader oraz file. Typy zmiennych:
- File – zmienne typu File reprezentują zbiór dyskowy czyli plik albo katalog. Polecenie utworzy instancję klasy File reprezentującą plik na dysku. W naszym przypadku zmienna file będzie reprezentowała plik o nazwie ”GameStats.txt” (fileName), w katalogu domowym użytkownika (filePath) . Utworzenie instancji klasy File nie oznacza utworzenia pliku fizycznie na dysku. Dlatego w dalszej części kodu sprawdzamy czy na dysku istnieje taka reprezentacja zbioru dyskowego (file.exists()) oraz czy jest to plik (file.isFile()). Jeżeli taki plik nie istnieje musimy go utworzyć (createNewFile()). Do zmiennej overalStats dodajemy informacje o najwyższym uzyskanym wyniku i jako że jest to pierwszy wpis, ustawiamy ilość punktów na 0. Natomiast jeżeli plik już istniał tworzymy instancje klasy FileReader
- FileReader – reprezentuje strumień danych. Dzięki klasie FileReader możemy odczytać znak po znaku z pliku tekstowego. Odczytywanie kolejnych znaków z pliku jest jednak niewygodne oraz mało wydajne, więc aby tego uniknąć możemy wykorzystać inny strumień danych: BufferedReader.
- BufferedReader - strumień danych, dzięki któremu możemy w łatwy sposób odczytywać kolejne linie w pliku tekstowym. Służy do tego metoda readLine
Pierwsza odczytana linia zostanie zapisana w zmiennej line typu String. Jeżeli odczytana linia nie jest pusta, to dzięki poleceniu Arrays.asList() wpis z pierwszej linii zostanie dodany do listy. Następne linie (wyniki poszczególnych graczy) są dodane do HashMapy players.
Po odczytaniu wszystkich danych z pliku i dodaniu ich do Map/List, możemy zakończyć odczyt i zamknąć strumień danych. Strumień danych powinien być zawsze zamknięty po skończeniu operacji na pliku, nawet jeżeli zostanie rzucony wyjątek. Dlatego kod zamknięcia strumienia jest umieszczony w bloku finally:
Dane z pliku
Odczytując dane z pliku musimy je gdzieś trzymać, aby program miał do nich łatwy dostęp. Pliki możemy modyfikować na bieżąco podczas działania programu, ale w naszym przypadku plik jest mały i wszystkie modyfikacje wykonamy w zmiennych, dwie podstawowe zmienne które wykorzystamy do operacji na danych, to Mapy[2] - a dokładniej HashMapy overalStats i players.:
dodatkowa zmienna, która będzie pomocna przy analizowaniu struktury pliku to Lista[3]:
- list - ta zmienna będzie przechowywała wartości z pierwszej lini naszego pliku. Każda wartość jest rozdzielona średnikiem ; (średnikiem).
Do rozdzielenia tych wartości po odczytaniu linii z pliku, użyjemy metody split oraz asList. Metoda split podzieli odczytana linię i zwróci tablicę z obiektami String. Aby tę tablicę przekonwertować do Listy wykorzystujemy Arrays.asList. Po tej operacji w liscie będą znajdowały się dwa elementy: “GraczsWithHighestScore=Krzysiek” oraz “HighestScore=10”
- overalStats - mapa, w której klucz i wartość są typu String. Trzymane są w niej podstawowe statystyki gry (pierwsza linia w pliku).
W naszym przypadku “GraczWithHighestScore” to klucz, natomiast “Krzysiek” to wartość przypisana do tego klucza. “HighestScore” jest też kluczem, do którego będzie przypisana wartość “10”. W tym celu sprawdzamy każdy element listy (pętla for) i ponownie korzystamy z funkcji split aby oddzielić klucz od wartości.
- players - Mapa zawierająca wyniki każdego zawodnika:
Kluczem tutaj jest imię zawodnika a wartość to jego najlepszy wynik.
W tym przypadku użyliśmy pętli while aby wczytać wszystkie wyniki do mapy players.
Zapis do pliku.
Za zapisanie statystyk do pliku będzie odpowiedzialna metoda saveStatsToFile, którą również umieszczamy w klasie Stats, podobnie jak metodę do odczytywania danych z pliku. Po zakończeniu gry, wszytkie zaktualizowane dane zostaną zapisane do pliku, z którego wcześniej były odczytane. Podobnie jak w metodzie readStatsFromFile, tak i tutaj do zapisania danych do pliku użyjemy trzech zmiennych:
Zmienna file jest typu File - został on opisany w poprzednim rozdziale. Tutaj zmienna file pełni taką samą rolę: reprezentuje zbiór danych na dysku.
opis metody saveStatsToFile
Główne klasy wykorzystane do zapisu do pliku:
- FileWriter- klasa ta działa analogicznie do klasy FileReader opisanej w poprzednim rozdziale, z tą różnicą, że tutaj znak po znaku jest zapisywany do pliku.
- BufferedWriter - klasa działa analogicznie jak BufferedReader, z tą różnicą, że tutaj ułatwia nam zapis - a nie odczyt - całej linii do pliku. .
Po zadeklarowaniu zmiennych następuje wywołanie metody
Ma ona za zadanie zaktualizować najlepszy wynik i zostanie opisana w następnym rozdziale.
Po aktualizacji wyniku przystępujemy do sprawdzenia czy plik ze statystykami istnieje - jeśli tak, to tworzymy obiekt klasy FileWriter.
Po utworzeniu obiektu FileWriter, aby łatwiej było dokonać zapisu do pliku, tworzymy obiekt BufferedWriter. W pierwszej linii zapisujemy informacje znajdujące się w zmiennej overalStats. Przy użyciu pętli for oraz obiektu StringBuilder tworzymy i zapisujemy pierwszą linię. Pierwsza linia jest odwzorowaniem mapy klucz=wartość; Dzięki metodzie keySet() możemy pobrać z mapy wszystkie klucze i przypisane im wartości.
Następna część kodu zapisuje do pliku listę graczy wraz z ich wynikami. Tutaj, podobnie jak z mapą overalStats, przydatna jest metoda keySet która zwróci wszystkie klucze (nazwy graczy) - dzięki temu możemy pobrać ich wyniki i zapisać je do pliku.
Podobnie jak z odczytem z pliku, tak i przy zapisie, musimy zamknąć strumień danych:
Metoda updateHighScore() i sortByScores()
Metoda updateHighScore wywoła kolejną metodę sortByScores, która posortuje Mapę z nazwami graczy i ich wynikami. Na tej podstawie zostanie też zaktualizowana mapa overalStats, gdzie trzymana jest nazwa gracza, który uzyskał najwięcej punktów (oraz jego wynik). Będzie to pierwszy zwrócony element ze zbioru
W metodzie sortByScores z mapy graczy pobrany jest zbiór elementów.
Następnie zbiór ten konwertowany jest do listy LinkedList
Taką listę możemy posortować używając komparatora. W naszym przypadku będziemy taką listę sortować po wyniku. Element z największym wynikiem będzie pierwszy i będzie zawierał również odnośnik do kolejnego elementu z wynikiem mniejszym.
Posortowaną listę Elementów zamieniamy na mapę LinkedHashMap (klucz=wartość). Pierwszy element dodany do tej mapy będzie miał najwyższy wynik.
Dodanie klasy Stats do gry
Aby statystyki zaczęły działać w naszej grze, musimy metody z klasy Stats wywołać w odpowiednich miejscach.
Po utworzeniu naszego boiska oraz po wprowadzeniu nazwy gracza, wywołanie metody getInstance() zwróci nam obiekt stats. Jest to pierwsze wywołanie metody w naszej grze, więc w tym momencie zostaną wczytane dane z pliku, ponieważ w konstruktorze klasy Stats jest wywołana metoda readStatsFromFile. Po wczytaniu danych klasa obiekt user wykorzysta wczytane dane do wyświetlenia najwyższego wyniku
W momencie kiedy gra się skończy, w obiekcie player zostanie wywołana metoda do zapisania wyników do pliku: