Implementacje - manio143/ShadowsOfShadows GitHub Wiki

Popatrzmy na ciekawsze elementy tego projektu. Po pierwsze, możemy zobaczyć podział na pewne moduły: Consoles, Entities, Generators, Items, Serialization, itd. Moduły Entities oraz Items opisują elementy gry: encje, które zobaczymy na ekranie oraz przedmioty, które znajdziemy w skrzyniach i które możemy dodać do naszego ekwipunku.

Dla nas ciekawe będą głównie trzy moduły: Consoles, Generators i Serialization.

  • Consoles - opisuje obiekty konsoli (dziedziczące po SadConsole.Console) oraz większość logiki gry
  • Serialization - opisuje mechanizm zapisywania stanu gry
  • Generators - opisuje losowe generatory przedmiotów, postaci i pokoi

Consoles

Naszym głównym ekranem jest obiekt klasy Screen, który tworzy pozostałe konsole i kontroluje ich aktualizację.

Jakoś tak wyszło, że pozostałe konsole utworzyły hierachię, co nie jest najlepszym przykładem dziedziczenia, ale było dość wygodne ze względu na reużywalność kodu.

MessageConsole > MenuConsole > StartScreen

W MessageConsole jest logika funkcjonowania konsoli w oparciu o wiadomości, które są zdefiniowane w pliku Messages.cs. Mamy kilka rodzajów wiadomości:

  • SimpleMessage - wypisuje na konsolę tekst i go tam zostawia, dopóki nie zostanie nadpisany kolejną wiadomością
  • WaitMessage - wypisuje na konsolę tekst i czeka aż zostanie naciśnięta spacja lub enter
  • ChoiceMessage - abstrakcyjna klasa zawierająca logikę wyświetlania zestawu opcji do wyboru
  • QuestionMessage - wybieramy odpowiedź na pytanie poprzez typ Enuma
  • ChestMessage - wybieramy przedmioty, które weźmiemy do ekwipunku
  • EquipmentMessage - wybieramy przedmioty z ekwipunku, które spożyjemy
  • TimeoutMessage - wypisuje na konsolę tekst i czeka pewien czas

W MessageConsole istnieje kolejka, do której dodajemy kolejne wiadomości, a następnie są one wyświetlane zgodnie z ich implementacją. Po zakończeniu wiadomości, czyli ustawieniu flagi Finished, wykonujemy akcję opisaną w polu PostProcessing i zdejmujemy kolejną wiadomość z kolejki w celu jej wyświetlenia.

MenuConsole korzysta z tych samych mechanizmów, dlatego dziedziczy po MessageConsole, a do tego zawiera implementację akcji i wyświetlania pozycji Menu oraz wyświetlanie statystyk gracza.

Serialization

W klasie GameState znajduje się wszystko co związane ze stanem gry: lista pokoi, gracz, generator pokoi, etc. Tą klasę serializujemy, żeby zapisać nasz stan gry. Do tego używamy biblioteki YamlDotNet.

W klasie Serializer mamy metodę Save(slot) i Load(slot), która zamienia nam enum SaveSlot na ścieżkę do pliku i zapisuje do niego lub odczytuje z niego zserializowane dane. Dodatkowo kompresujemy nasz plik YAMLowy za pomocą GZipStream, żeby zajmował mniej miejsca i nie był łatwo edytowalny dla zwykłego użytkownika.

Dlaczego YamlDotNet? Próbowałem również innych serializatorów, ale tym najprościej osiągnąłem to co chciałem, choć nie jestem z niego w stu procentach zadowolony (np. musiałem upublicznić kilka prywatnych pól i zmienić je na własności). Ogólnie czuję, że na tej płaszczyźnie jeszcze brakuje nam serializatora dla dotnetu z dużą konfigurowalnością.

Generators

Nie tu jakiejś skomplikowanej magii. W przypadku przedmiotów i postaci, bierzemy wszystkie typy danego rodzaju i losujemy jeden z nich z prawdopodobieństwem opisanym przez dystrybucję z biblioteki MathNet. Następnie tworzymy jego instancję i ją zwracamy.

Z pokojem jest nieco inaczej. Każdy pokój musi dotykać poprzedniego tak żeby można było przechodzić drzwiami między nimi. Więc generator pamięta ostatni pokój. Najpierw generujemy prostokąt pokoju - ściany, a potem z dystrybucją Bernoulliego losujemy do 6 potworów i skrzyń w pokoju i losowo je w nim umieszczamy.