2008-12-11 10 views
88

Dla zabawy staram się napisać jedną z ulubionych gier planszowych mojego syna jako oprogramowanie. W końcu spodziewam się zbudować na nim interfejs WPF, ale teraz buduję maszynę, która modeluje gry i jego reguły.Wszelkie wzorce do modelowania gier planszowych?

Kiedy to robię, ciągle widzę problemy, które moim zdaniem są powszechne w wielu grach planszowych, i być może inni już rozwiązali je lepiej niż ja.

(. Zauważ, że AI do gry, a wzory wokół wysokiej wydajności nie są interesujące dla mnie)

Dotychczas moje wzory są:

  • Kilka niezmienne typy reprezentujące podmioty w grze pole, np kości, warcaby, karty, zarządu, obowiązuje na płycie, pieniądze, itp

  • Obiekt dla każdego gracza, który zawiera zasoby graczy (np pieniądze, wynik), ich nazwy, itp

  • Obiekt reprezentujący stan gry: odtwarzacze, kto nim jest, układ peices na planszy itp.

  • Maszyna stanowa, która zarządza sekwencją zakrętów. Na przykład wiele gier ma małą grę przed meczem, w której każdy gracz rzuca na turę, aby zobaczyć, kto jako pierwszy zagra; to jest stan początkowy. Kiedy zaczyna się kolejka gracza, najpierw toczy się, następnie porusza się, następnie musi zatańczyć w miejscu, a inni gracze odgadują, z jakiej rasy są, a następnie otrzymują punkty.

Czy jest jakiś wcześniejszy stan wiedzy, z którego mogę skorzystać?

EDIT: Jedno niedawno zdałem sobie sprawę, że stan gry można podzielić na dwie kategorie:

  • Gra stan artefakt. "Mam 10 USD" lub "moja lewa ręka jest na niebiesko".

  • Stan sekwencji gier. "Dwukrotnie wyrzuciłem debel, a drugi wsadził mnie do więzienia". Maszyna państwowa może mieć tutaj sens.

EDIT: Co ja naprawdę szukam tutaj jest najlepiej sposobem wdrożenia multiplayer turowa gry, takie jak szachy czy Scrabble czy Monopoly. Jestem pewien, że mogłem stworzyć taką grę, po prostu pracując nad nią od początku do końca, ale, podobnie jak inne Wzorce Projektowe, prawdopodobnie istnieją pewne sposoby, aby wszystko przebiegło znacznie sprawniej, co nie jest oczywiste bez dokładnego przestudiowania. Tego właśnie oczekuję.

+2

Budujesz coś w rodzaju: Hokey Pokey, Monopoly, szarady? –

+0

Będziesz potrzebować automatu stanu dla każdej reguły, która zależy od stanu (err ...), podobnie jak reguła trzech podwójnych monet dla Monopoly. Dodałbym pełniejszą odpowiedź, ale nie mam doświadczenia w tym. Mogłem jednak pontyfikować. – MSN

Odpowiedz

107

wydaje się, że jest to 2-miesięczny wątek, który właśnie zauważyłem, ale co do cholery. Wcześniej zaprojektowałem i opracowałem strukturę rozgrywki dla komercyjnej, sieciowej gry planszowej. Mieliśmy bardzo przyjemne doświadczenie w pracy z tym.

Twoja gra może znajdować się w (blisko) nieskończonej ilości stanów z powodu permutacji takich rzeczy, jak ilość posiadanego gracza A, ile pieniędzy ma gracz B, itd. ... Dlatego jestem całkiem pewnie chcesz trzymać się z dala od automatów państwowych.

Ideą naszego systemu było przedstawienie stanu gry jako struktury ze wszystkimi polami danych, które razem, zapewniają kompletny stan gry (tj. Jeśli chcesz zapisać grę na dysku, wypiszesz tę strukturę).

Użyliśmy Command Pattern do reprezentowania wszystkich ważnych działań w grze, które może wykonać gracz. Tutaj byłoby przykładem działania:

class RollDice : public Action 
{ 
    public: 
    RollDice(int player); 

    virtual void Apply(GameState& gameState) const; // Apply the action to the gamestate, modifying the gamestate 
    virtual bool IsLegal(const GameState& gameState) const; // Returns true if this is a legal action 
}; 

Więc widać, że do podjęcia decyzji, czy ruch jest ważny, można skonstruować, że działania, a następnie wywołać jej IsLegal funkcję, przekazując aktualnego stanu gry. Jeśli jest ona ważna, a gracz potwierdza akcję, możesz wywołać funkcję Zastosuj, aby faktycznie zmodyfikować stan gry.Zapewniając, że twój kod gry może jedynie modyfikować stan gry, tworząc i przesyłając akcje prawne (innymi słowy, rodzina Action :: Apply jest jedyną rzeczą, która bezpośrednio modyfikuje stan gry), wtedy upewniasz się, że twoja gra stan nigdy nie będzie nieważny. Ponadto, używając wzorca poleceń, umożliwiasz szeregowanie żądanych ruchów gracza i wysyłanie ich przez sieć, która ma być wykonana w stanach innych graczy.

Skończyło się na tym, że jeden system otrzymał dość eleganckie rozwiązanie. Czasami działania miałyby dwie lub więcej faz. Gracz może na przykład wylądować na nieruchomości w Monopolu i musi teraz podjąć nową decyzję. Jaki jest stan gry między momentem, w którym gracz rzucił kostką, a zanim zdecyduje się na zakup nieruchomości, czy nie? Poradziliśmy sobie z takimi sytuacjami, przedstawiając członka naszego stanu gry jako "Kontekst działania". Kontekst działania będzie normalnie zerowy, co oznacza, że ​​gra nie jest obecnie w żadnym szczególnym stanie. Kiedy gracz rzuca kośćmi, a ruch kroczący zostanie zastosowany do stanu gry, uświadomi sobie, że gracz wylądował na nieprzyjętej własności i może stworzyć nowy kontekst akcji "PlayerDecideToPurchaseProperty" zawierający indeks gracza czekamy na decyzję od. Do czasu zakończenia działania RollDice nasz stan gry oznacza, że ​​obecnie oczekuje, że określony gracz zdecyduje, czy kupić nieruchomość. Teraz wszystkim innym czynnościom można powrócić do metody IsLegal, z wyjątkiem działań "BuyProperty" i "PassPropertyPurchaseOpportunity", które są legalne tylko wtedy, gdy stan gry ma kontekst działania "PlayerDecideToPurchaseProperty".

Dzięki użyciu kontekstów akcji, nigdy nie ma jednego punktu w czasie życia gry planszowej, w której struktura stanu gry nie reprezentuje DOKŁADNIE tego, co dzieje się w grze w tym momencie. Jest to bardzo pożądana właściwość twojego systemu gier planszowych. Łatwiej będzie napisać kod, gdy znajdziesz wszystko, co chcesz wiedzieć o tym, co dzieje się w grze, badając tylko jedną strukturę.

Co więcej, bardzo ładnie rozciąga się na środowiska sieciowe, w których klienci mogą przesyłać swoje akcje przez sieć do komputera-hosta, który może zastosować akcję do "oficjalnego" stanu gry hosta, a następnie powtórzyć tę akcję z powrotem do wszystkich inni klienci mają je zastosować do ich replikowanych stanów gry.

Mam nadzieję, że było to zwięzłe i pomocne.

+3

Nie sądzę, że jest zwięzły, ale jest pomocny! Rewizja. –

+0

Cieszę się, że było pomocne ... Które części nie były zwięzłe? Byłbym szczęśliwy, robiąc edycję wyjaśnień. –

+0

W tej chwili buduję turową grę, a ten post był naprawdę pomocny! – Kiv

3

Three Rings oferuje LGPL dla bibliotek Java. Nenya i Vilya to biblioteki związane z grami.

Oczywiście pomógłbyś, gdyby twoje pytanie dotyczyło ograniczeń platformy i/lub języka.

+0

"Ostatecznie oczekuję zbudowania interfejsu użytkownika WPF" - co oznacza .NET.Przynajmniej, o ile wiem. –

+0

Zupa z alfabetu, której nie znam. – jmucchiello

+0

Tak, robię .NET, ale moje pytanie nie jest specyficzne dla języka lub platformy. –

8

Oczywiście istnieje wiele, wiele, wiele, wiele, wiele, wiele, wiele zasobów na ten temat. Ale myślę, że jesteś na właściwej ścieżce dzielącej obiekty i pozwalającej im obsługiwać własne zdarzenia/dane i tak dalej.

Grając w gry planszowe na kafelkach, dobrze jest mieć procedury mapowania między tablicą planszy i wierszem/kolorem iz powrotem, a także inne funkcje. Pamiętam moją pierwszą grę planszową (dawno dawno temu), kiedy struggeled z jak dostać wiersza/COL z boardarray 5.

1 2 3 
4 (5) 6 BoardArray 5 = row 2, col 2 
7 8 9 

nostalgii. ;)

W każdym razie, http://www.gamedev.net/ jest dobrym miejscem dla informacji. http://www.gamedev.net/reference/

+0

Dlaczego po prostu nie użyjesz 2-wymiarowej tablicy? Wtedy kompilator poradzi sobie z tym za Ciebie. –

+0

Moja wymówka jest taka, że ​​to było dawno, dawno temu. ;) – Stefan

+1

gamedev ma tonę, jeśli coś, ale nie widziałem całkiem tego, czego szukałem. –

17

Podstawowa struktura silnika gry wykorzystuje numer State Pattern. Elementy twojego pudełka gry to singletons różnych klas. Struktura każdego stanu może używać Strategy Pattern lub the Template Method.

A Factory służy do tworzenia graczy, którzy są umieszczani na liście graczy, inny singleton. GUI będzie czuwać nad Game Engine za pomocą Observer pattern i wchodzić z nim w interakcję, używając jednego z kilku obiektów Command utworzonych przy użyciu Command Pattern. Używanie Observer i Command może być używane w kontekście Passive View. Jednak w zależności od twoich preferencji można użyć niemal dowolnego wzorca MVP/MVC. Podczas zapisywania gry musisz pobrać memento z bieżącego stanu.

Zalecam przejrzenie niektórych wzorów na tym site i zobaczenie, czy któryś z nich przyjął Cię jako punkt wyjścia. Znowu sercem twojej planszy będzie maszyna państwowa. Większość gier będzie reprezentowana przez dwa stany sprzed gry/konfiguracji i rzeczywistą grę. Ale możesz mieć więcej stanów, jeśli gra, którą modelujesz, ma kilka różnych trybów gry. Stany nie muszą być sekwencyjne, na przykład w grze wojennej Axis & Battles ma planszę bitewną, którą gracze mogą wykorzystać do rozstrzygania bitew. Tak więc istnieją trzy stany przed grą, plansza główna, plansza bitewna z grą stale przełączającą się pomiędzy tablicą główną a planszą bitwy. Oczywiście kolejność obrotów może być również reprezentowana przez automat stanów.

5

Większość materiałów, które mogę znaleźć w Internecie, to listy opublikowanych referencji. Sekcja publikacji Game Design Patterns zawiera linki do wersji PDF artykułów i prac dyplomowych. Wiele z nich wygląda jak dokumenty akademickie, takie jak Design Patterns for Games. Istnieje również jedna książka dostępna w Amazon, Patterns in Game Design.

13

Właśnie skończyłem projektowanie i wdrażanie gry opartej na stanie z wykorzystaniem polimorfizmu.

stosując zasadę abstrakcyjne klasy o nazwie GamePhase który ma jedną ważną metodę

abstract public GamePhase turn(); 

Oznacza to każdy GamePhase Obiekt posiada aktualnego stanu gry, a wezwanie do turn() patrzy na jego obecnym stanie i zwrotów następny GamePhase.

Każdy beton GamePhase ma konstruktorów, które utrzymują stan gry w całości. Każda metoda turn() ma w sobie trochę reguł gry. Rozprzestrzenia to zasady, ale zbliża powiązane zasady. Końcowym rezultatem każdego turn() jest właśnie utworzenie kolejnego GamePhase i przejście w pełnym stanie do następnej fazy.

Dzięki temu turn() może być bardzo elastyczny. W zależności od gry dany stan może rozgałęziać się na wiele różnych typów faz. Tworzy to wykres wszystkich faz gry.

Na najwyższym poziomie kodu do prowadzenia go jest bardzo proste:

GamePhase state = ...initial phase 
while(true) { 
    // read the state, do some ui work 
    state = state.turn(); 
} 

Jest to niezwykle użyteczne jako Mogę teraz łatwo stworzyć dowolny stan/fazę gry do testowania

teraz aby odpowiedzieć na drugą część pytania, jak to działa w trybie multiplayer? W ramach pewnych GamePhase s, które wymagają wprowadzania danych przez użytkownika, wywołanie z turn() prosi bieżący numer Player o ich Strategy przy aktualnym stanie/fazie. Strategy to tylko interfejs wszystkich możliwych decyzji, które może podjąć Player. Ta konfiguracja pozwala również na implementację Strategy z AI!

także Andrew Top powiedział:

Gra może prawdopodobnie być w (prawie) nieskończona ilość członkowskie ze względu na permutacji rzeczy jak, ile pieniędzy gracz A ma, ile pieniędzy gracz B ma, i tak dalej ... Dlatego jestem pewien, że chcesz trzymać się z dala od automatów państwowych.

Myślę, że to stwierdzenie jest bardzo mylące, podczas gdy prawdą jest, że istnieje wiele różnych stanów gry, jest tylko kilka faz gry. Aby obsłużyć jego przykład, wszystko to będzie całkowitym parametrem dla konstruktorów mojego konkretnego GamePhase s.

monopol

przykład pewnych GamePhase s to:

  • GameStarts
  • PlayerRolls
  • PlayerLandsOnProperty (FreeParking, GoToJail Idź, etc)
  • PlayerTrades
  • PlayerPurchasesProperty
  • PlayerPurchasesHouses
  • PlayerPurchasesHotels
  • PlayerPaysRent
  • PlayerBankrupts
  • (All szansa i Community Chest cards)

Oraz niektóre stany w bazie GamePhase są:

  • Lista Graczy
  • Aktualny Player (kto skręcić)
  • Gracza Pieniądze/Property
  • Domy/Hotele na właściwości
  • Pozycja zawodnika

a następnie kilka faz by nagrać swój własny stan w miarę potrzeb, na przykład PlayerRolls będzie nagrywać liczba razy, w których gracz rzuca kolejnymi debelami. Gdy opuszczymy fazę PlayerRolls, nie będziemy już przejmować się kolejnymi rzutami.

Wiele faz można ponownie wykorzystać i połączyć ze sobą. Na przykład GamePhaseCommunityChestAdvanceToGo utworzy następną fazę PlayerLandsOnGo z bieżącym stanem i zwróci ją. W konstruktorze PlayerLandsOnGo obecny gracz zostanie przeniesiony do Go, a ich pieniądze zostaną zwiększone o 200 $.

1

Czy jest jakiś wcześniejszy stan wiedzy, z którego mogę skorzystać?

Jeśli twoje pytanie nie jest specyficzne dla danego języka lub platformy. wtedy zaleciłbym rozważyć Wzorce AOP dla stanu, Memento, Command, itp.

Co to jest odpowiedź .NET na AOP ???

spróbować także znaleźć jakieś fajne strony internetowe, takie jak http://www.chessbin.com

2

zgadzam się z odpowiedzią Pyrolistical i wolę jego sposób robienia rzeczy (właśnie odtłuszczonego innych odpowiedzi chociaż).

Przypadkowo również użyłem jego nazwy "GamePhase". Zasadniczo to, co zrobiłbym w przypadku turowej gry planszowej, to to, że twoja klasa GameState zawiera obiekt abstrakcyjnej GamePhase, jak wspomniano w Pyrolistical.

Powiedzmy stany gry są:

  1. Rolka
  2. Move
  3. Kup/Nie kupuj
  4. Jail

Można mieć konkretnych klas pochodnych dla każdego państwa . Posiada funkcje wirtualne przynajmniej dla:

StartPhase(); 
EndPhase(); 
Action(); 

W StartPhase() funkcjonować można ustawić wszystkie wartości początkowe dla państwa, na przykład wyłączenie wejście drugi gracz i tak dalej.

Po wywołaniu roll.EndPhase() upewnij się, że wskaźnik GamePhase zostanie ustawiony w następnym stanie.

phase = new MovePhase(); 
phase.StartPhase(); 

W tym MovePhase :: StartPhase() byś na przykład ustawić pozostałe ruchy aktywny gracz do wysokości walcowanych w poprzedniej fazie.

Teraz z tym projektem możesz rozwiązać problem "3 x podwójne = więzienie" w fazie Roll. Klasa RollPhase może obsłużyć własny stan. Na przykład:

GameState state; //Set in constructor. 
Die die;   // Only relevant to the roll phase. 
int doublesRemainingBeforeJail; 
StartPhase() 
{ 
    die = new Die(); 
    doublesRemainingBeforeJail = 3; 
} 

Action() 
{ 
    if(doublesRemainingBeforeJail<=0) 
    { 
     state.phase = new JailPhase(); // JailPhase::StartPhase(){set moves to 0};    
     state.phase.StartPhase(); 
     return; 
    } 

    int die1 = die.Roll(); 
    int die2 = die.Roll(); 

    if(die1 == die2) 
    { 
     --doublesRemainingBeforeJail; 
     state.activePlayer.AddMovesRemaining(die1 + die2); 
     Action(); //Roll again. 
    } 

    state.activePlayer.AddMovesRemaining(die1 + die2); 
    this.EndPhase(); // Continue to moving phase. Player has X moves remaining. 
} 

Różniam się od pirolitycznego tym, że powinien istnieć etap dla wszystkiego, w tym kiedy gracz ląduje na skrzyni Wspólnoty lub coś w tym stylu. Poradziłbym sobie z tym wszystkim w fazie MovePhase. Dzieje się tak, ponieważ jeśli masz zbyt wiele kolejnych faz, gracz prawdopodobnie będzie czuł się zbyt "prowadzony". Na przykład, jeśli istnieje faza, w której gracz może TYLKO kupować nieruchomości, a następnie TYLKO kupować hotele, a następnie TYLKO kupować domy, tak jak nie ma wolności. Po prostu zatrzaskuj wszystkie te części w jedną BuyPhase i daj graczowi swobodę kupowania czegokolwiek, co chce. Klasa BuyPhase może z łatwością kontrolować, które zakupy są legalne.

Wreszcie, zaadresujmy się do planszy gry. Chociaż tablica 2D jest w porządku, zaleciłbym posiadanie wykresu kafelkowego (gdzie kafelek jest pozycją na planszy). W przypadku monopoli będzie to raczej lista podwójnie powiązana. Następnie każda dachówka miałaby:

  1. previousTile
  2. nextTile

Więc byłoby znacznie łatwiej zrobić coś takiego:

While(movesRemaining>0) 
    AdvanceTo(currentTile.nextTile); 

Funkcja AdvanceTo może obsługiwać swój krok krok animacje lub cokolwiek chcesz. A także zmniejszaj pozostałe ruchy oczywiście.

Zalecenia RS Conleya dotyczące wzorca obserwacyjnego dla GUI są dobre.

Nie napisałem dużo wcześniej. Mam nadzieję, że to pomaga komuś.