Ktulu Online - szkic architektury

Pierwsza notka konkursowa za nami, czas zacząć rozwijać projekt - a od czego zacząć, jeśli nie od choćby bardzo ogólnego zarysu architektury? Doświadczenie uczy, że rzucenie się w wir kodowania bez cienia planu potrafi prowadzić co prawda do szybkiego powstania programu, który coś robi, ale dalszy rozwój staje się coraz trudniejszy. Nie chciałbym utknąć w połowie projektu, stwierdzając że muszę zacząć od nowa, więc najpierw zastanowię się, jak by go najlepiej skonstruować.

Po pierwsze, głównym celem projektu jest umożliwienie prowadzenia rozgrywek przez internet - będziemy więc mieć do czynienia z jakiegoś rodzaju aplikacją sieciową. Jeden z graczy będzie wobec tego uruchamiał serwer, a pozostali będą mogli podłączać się do niego jako klienci. Najwygodniejsze będzie zapewne zebranie i serwera, i klienta w jeden plik wykonywalny (dzięki czemu hostujący grę nie będzie musiał uruchamiać kilku programów), ale to już niezbyt istotny szczegół. Najważniejszy jest podział ról między serwer a klienty.

[Aktualizacja: przed dalszym czytaniem polecam zapoznać się z zasadami gry.]

Role serwera i klienta

W oryginalnej grze mamy prowadzącego (zwanego "Manitou"), który wszystko kontroluje - wie, kto jest kim, kto posiada posążek (w odróżnieniu od mafii, w której chodzi wyłącznie o wybicie przeciwników, w Ktulu większość celów gry obraca się wokół posążka, który z początku posiadają Bandyci), każe graczom zamykać lub otwierać oczy, zlicza głosy w głosowaniach. Ta rola będzie pełniona przez serwer - będzie on przechowywał pełen stan gry i wysyłał odpowiednie polecenia i informacje klientom. Jedyne co będą robić klienty, to powiadamiać serwer o decyzjach dotyczących wykonywanych przez siebie czynności.

Komunikacja między Manitou i graczami sprowadza się tak naprawdę do kilku prostych rzeczy. Manitou może:

  • (jak już wspomniałem) kazać graczowi zamknąć/otworzyć oczy (w języku gry: zasnąć/wstać)
  • spytać gracza o wybór dotyczący jego specjalnej zdolności, co w przypadku wszystkich postaci sprowadza się do wskazania osoby (o ile gracz chce wykorzystać zdolność)
  • poprosić gracza o głos (w pojedynku lub głosowaniu dot. przeszukania/kary śmierci na koniec dnia)

I to w zasadzie wszystko. W przypadku niektórych zdolności potrzeba przekazać informację zwrotną, ale to będziemy robić na żądanie gracza (o czym dalej).

Gracz natomiast może:

  • odzywać się, o ile nie śpi i jest dzień - w nocy komunikacja między obudzonymi graczami będzie ograniczona, aby odzwierciedlić konieczność zachowania ciszy w rzeczywistej grze
  • dokonywać wyborów, o które poprosił go Manitou, tj. wykorzystać zdolność (i ewentualnie otrzymać informację zwrotną w odpowiedzi) lub zagłosować

I... chyba tyle. Nie wygląda to skomplikowanie, ale wystarcza do stworzenia naprawdę ciekawej gry.

Przykładowy przebieg początku gry wyglądałby wtedy tak:

  1. Manitou każe wszystkim zasnąć.
  2. Manitou budzi Dziwkę i prosi o skorzystanie ze zdolności (Dziwka nie może wstrzymać się od użycia zdolności).
  3. Dziwka wysyła Manitou wybór osoby, którą chce sprawdzić i otrzymuje jej tożsamość w odpowiedzi.
  4. Manitou każe zasnąć Dziwce (opcjonalnie: podobnie pozwala skorzystać ze zdolności Szeryfowi i Pastorowi) i budzi Bandytów.
  5. Bandyci, ponieważ wszyscy nie śpią, poznają swoje tożsamości. Manitou prosi Herszta o wybór osoby trzymającej posążek i otrzymuje ją w odpowiedzi.
  6. Manitou każe zasnąć Bandytom i budzi Indian.
  7. Indianie poznają swoje tożsamości i idą spać.
  8. Manitou budzi Ufoli (o ile są w grze), pozwala się im poznać i każe im iść spać.
  9. Manitou ogłasza pierwszy dzień i budzi wszystkich.
  10. Gracze rozpoczynają dyskusję.
  11. ...

Jak widać, tych kilka prostych komunikatów pozwoliłoby na bezproblemowe przeprowadzenie zerowej nocy i rozpoczęcie pierwszego dnia.

Kwestie techniczne

Nasuwający się pomysł na implementację to zestaw dwóch cech (ang. traits) - dla nieznających Rusta, jest to coś, co przypomina znane z Javy czy C# interfejsy, ale różni się w kilku szczegółach: m.in. metoda cechy może mieć domyślną implementację oraz cechy mogą zawierać metody, które w Javie/C# określilibyśmy jako statyczne. Wracając do meritum, cechy odpowiadałyby graczowi oraz Manitou - cecha gracza zawierałaby metody, które wywoływałby Manitou (typu idz_spac(), obudz_sie(), uzyj_zdolnosci()), natomiast Manitou udostępniałby metody, pozwalające graczowi wykonywać jego zadania (powiedz_cos(), wybierz_osobe()).

Każda z cech byłaby zaimplementowana parze struktur: jednej reprezentującej gracza/Manitou i drugiej, stanowiącej obiekt proxy do wywoływania odpowiednich metod przez sieć (wywołań RPC).

Dzięki takiej architekturze zarówno obiekt gracza, jak i obiekt Manitou nie musiałyby się interesować, czy komunikują się z drugą stroną bezpośrednio, czy przez sieć. To pozwoliłoby więc na identyczną ich implementację we właściwej aplikacji, jak i w automatycznych testach, co jest dużym plusem.

Do samych wywołań RPC dałoby się być może wykorzystać jakąś gotową bibliotekę. Wygląda na to, że ciekawą stworzyło Google (tarpc). Jest duża szansa, że skorzystanie z niej pozwoli mocno przyspieszyć rozwój projektu.

Podsumowanie

Nie powiedziałem tutaj ani słowa o interfejsie, ale zrobiłem to celowo. W pierwszej kolejności chcę się skupić na implementacji zasad gry i komunikacji między klientem a serwerem w postaci samodzielnej biblioteki. Gdy będzie ona gotowa, będzie można ją wykorzystać we właściwej aplikacji, która udostępni graczowi jakiś interfejs. To jest jednak zadanie na dalszą przyszłość.

Kod projektu zaczyna powoli powstawać na GitHubie - zapraszam do obserwowania postępów :)