Wzorzec projektowy dla gry planszowej

Wzorzec projektowy dla gry planszowej
WC
  • Rejestracja: dni
  • Ostatnio: dni
0

Piszę grę planszową 2D w C++ obiektowo. Jaki wzorzec architektoniczny wybrać?

Wstępne założenia

  1. Można podpiąć dowolną warstwę prezentacji, np. WinAPI, GDI, Qt.
  2. Łatwa konwersja na inną platformę, np. Linux.
  3. Obiekty nie wchodzą sobie w kompetencje - wykonują tylko swoje zadania.

Zacząłem pisać grę w WinAPI i rysować planszę za pomocą GDI. Prawdopodobnie zmienię na MFC, bo pisanie z palca całego interfejsu i innych okien z komponentami jest czasochłonne. Z drugiej strony tworząc coś w MFC można się zamęczyć. Biblioteki wykorzystujące cały czas 100% czasu procesora odpadają.

Założenie drugie to niedyskryminowanie innych platform niż Windows. I tu się zaczyna. Dylemat jest następujący - pisać spójną aplikację czy z podziałem na warstwy?

  1. Czy warstwa logiki ma operować bezpośrednio na obiektach okna?
  2. Jak interfejs ma komunikować się z warstwą logiki i pobierać dane?

Przykładowe problemy

  1. Kiedy gracz wykonuje ruch, warstwa logiki musi się o tym dowiedzieć i wykonać obliczenia.
  2. Kiedy komputer wykonuje ruch, pionki na planszy muszą się przestawić.
  3. Zatem jest potrzebna komunikacja obustronna.

Przykłady komunikacji

  1. Fasady - każda warstwa ma fasadę, czyli specjalny interfejs, przez który poszczególne warstwy mogą komunikować się. Zarówno model i widok mają funkcje, które mogą wywoływać nawzajem (może ich być dużo).
Kopiuj
model.przesun(pionek, pozycja); //widok każe modelowi wykonać akcję
widok.koniecGry(); //model informuje widok o końcu gry
  1. MVC - komunikacja model-widok jest jednostronna. Widok odwołuje się do modelu i nie na odwrót. A co wtedy, gdy model musi wykonać akcję w widoku, np. przesunąć pionek na planszy lub powiadomić, że gra się skończyła? Można zastosować wzorzec Obserwator. Albo inaczej - widok nadzoruje model.
Kopiuj
if(model.przesun(pionek, pozycja))
{
   this.przesun(pionek, pozycja); //wewnątrz widoku
}
while(akcja = model.getAction()) {} //można tak
if(model.zaznacz(pionek))
  if(model.pokazDozwolone(...)) {} //albo tak
if(model.wykonajRuchy()) {}
  1. Zdarzenia - można wysłać zdarzenie do okna, ale wtedy uzależnimy logikę od widoku. Innym sposobem jest utworzenie metod dla zdarzeń w obu warstwach albo wykorzystanie wzorca Obserwator.. Zdarzenia trzeba nazwać i obsłużyć instrukcją switch.
Kopiuj
switch(zdarzenie)
{
   case EVENT_FINISH: this.finish(); break; 
   default: /* błąd */
}
  1. Wolne obiekty - nie ma ścisłego podziału na warstwy. Poszczególne obiekty mogą odwoływać się do publicznych metod i własności innych obiektów. Wyjątkiem są obiekty, które z założenia mogą być obsługiwane tylko przez 1 klasę lub metodę. To jednak nie rozwiązuje problemów z implementacją!

Jeszcze jedna rzecz
Zdarzenie WM_PAINT odrysowuje okno. Trzeba narysować aktualny stan planszy, pionki, a przy tym użyć właściwych kolorów lub bitmap. Kiedy zaznaczamy pionka, też trzeba to uwidocznić np. innym obramowaniem pola. Podobnie ostatni ruch oraz podpowiedzi, gdzie wolno przesunąć pionek. Pojawiają się pytania:

  1. Jak odczytać położenie pionków z klasy bez obciążenia procesora?
  2. Jaka warstwa ma przechowywać informacje o kolorach pól i graczy?

Ad 1. Aktualnie w klasie Gra mam własność int pola[10][10]. Powiecie, że powinna ona być prywatna. W końcu implementacja może się zmienić i zamiast tablicy liczb całkowitych będzie tablica obiektów Pole pola[10][10] bądź zostanie przeniesiona do innej klasy, cokolwiek. Z drugiej strony gdyby co chwilę wywoływać metody getCzyjePole(), getKolorPola(), getCosTam() - za duże obciążenie dla procesora.

Kopiuj
for(x=0; x<10; x++)
   for(y=0; y<10; y++)
      switch(model.getCzyjePole(x, y)) {...}

A skąd wiem, ile jest pól? Że plansza jest kwadratowa? Jak zmienimy planszę na okrągłą, nic nie da zamiana klasy w modelu z PlanszaKwadrat na PlanszaKolo, bo trzeba wymienić cały widok. To tylko przykład, aby pokazać paradoks MVC. Nie da się całkowicie wyeliminować logiki (biznesu) programu z widoku.

Ad 2. Takie rzeczy zwykle podajemy w ustawieniach. A ich nie powierzymy widokowi. Równie dobrze widokiem może być tryb tekstowy bez kolorów. 2 pliki ustawień (drugi dla widoku, jak ma wyglądać plansza)? No way!

Pole może należeć do gracza lub być puste; być zaznaczone lub nie; być oznaczone jako ostatnie przesunięcie lub nie; jako podpowiedź lub nie - zatem gdzieś trzeba trzymać stany pól. Model lub widok. Bitwise operators? Obiekt Pole, a tam własności to opisujące? Hm? Sugestie?

Chyba wszystko już wyjaśniłem. Mam mało czasu na napisanie gry, chcę to zrobić dobrze, piszę głównie dla Windowsa. Jak poszczególne obiekty powinny się komunikować?

Azarien
  • Rejestracja: dni
  • Ostatnio: dni
1

Dylemat jest następujący - pisać spójną aplikację czy z podziałem na warstwy?
Co to znaczy „spójną”? Spójna to taka, w której są podziały na warstwy. Inaczej dostajesz spagetti.

Mniejsza o wzorce, bo nie jestem ich wyznawcą. Ale kod powinien być taki, że bierzesz moduł logiki, i bez zmian możesz go zastosować w zupełnie innym GUI, czy nawet programie bez GUI (konsolowym czy serwerowym).

A co wtedy, gdy model musi wykonać akcję w widoku, np. przesunąć pionek na planszy lub powiadomić
funkcja logiki zwraca ruch jako rezultat funkcji, albo wywołuje zdarzenie GUI (callback).

Widok odwołuje się do modelu i nie na odwrót.
Może na odwrót, zgodnie z
Poszczególne obiekty mogą odwoływać się do publicznych metod i własności innych obiektów.
ale pod warunkiem, że interfejsy z obu stron będą ściśle przez ciebie określone i niezależne od platformy ani biblioteki GUI.

Tu znowu widzę że trzymanie się wzorców jest ograniczające. W każdym przytoczonym przez ciebie widzisz wady, i to że nie do końca odpowiadają temu, co chcesz osiągnąć.
Podział na warstwy jest dobry. Ale ścisłe trzymanie się zasad wzorcowych typu Takie rzeczy zwykle podajemy w ustawieniach. A ich nie powierzymy widokowi powoduje, że się blokujesz, bo nie ma wzorca „moja gra planszowa”.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.