Dla zabawy próbuję napisać jedną z ulubionych gier planszowych mojego syna jako oprogramowanie. W końcu spodziewam się zbudowania na nim interfejsu użytkownika WPF, ale teraz buduję maszynę, która modeluje gry i jej zasady.
Robiąc to, ciągle widzę problemy, które moim zdaniem są wspólne dla wielu gier planszowych, a być może inni rozwiązali je już lepiej niż ja.
(Zauważ, że sztuczna inteligencja do grania w grę i wzorce wokół wysokiej wydajności nie są dla mnie interesujące).
Jak dotąd moje wzorce to:
Kilka niezmiennych typów reprezentujących elementy w pudełku gry, np. Kości, warcaby, karty, plansza, pola na planszy, pieniądze itp.
Obiekt dla każdego gracza, który zawiera zasoby gracza (np. Pieniądze, punkty), jego nazwę itp.
Obiekt, który reprezentuje stan gry: gracze, kto jest w kolejce, układ elementów na planszy itp.
Maszyna stanowa zarządzająca sekwencją tur. Na przykład wiele gier ma małą przedmeczę, w której każdy gracz rzuca, aby zobaczyć, kto pierwszy; to jest stan początkowy. Kiedy zaczyna się tura gracza, najpierw rzuca, potem się porusza, potem musi tańczyć w miejscu, potem inni gracze odgadują, jakiego rodzaju kurczaków jest, a potem otrzymują punkty.
Czy jest jakiś stan wiedzy, z którego mogę skorzystać?
EDYCJA: Ostatnio zdałem sobie sprawę, że stan gry można podzielić na dwie kategorie:
Stan artefaktów w grze . „Mam 10 dolarów” lub „Moja lewa ręka jest na niebiesko”.
Stan sekwencji gry . „Dwa razy wyrzuciłem dublety; za następnym trafiam do więzienia”. Maszyna stanów może mieć tutaj sens.
EDYCJA: To, czego naprawdę szukam, to najlepszy sposób na zaimplementowanie gier turowych dla wielu graczy, takich jak Chess, Scrabble lub Monopoly. Jestem pewien, że mógłbym stworzyć taką grę, po prostu pracując nad nią od początku do końca, ale, podobnie jak inne wzorce projektowe, prawdopodobnie istnieją sposoby, aby wszystko przebiegało znacznie płynniej, które nie są oczywiste bez dokładnego zbadania. Na to mam nadzieję.
źródło
Odpowiedzi:
wygląda na to, że to wątek sprzed 2 miesięcy, który właśnie zauważyłem, ale co do cholery. Wcześniej zaprojektowałem i opracowałem ramy rozgrywki dla komercyjnej, sieciowej gry planszowej. Praca z nim była bardzo przyjemna.
Twoja gra może prawdopodobnie znajdować się w (prawie) nieskończonej liczbie stanów z powodu permutacji takich rzeczy, jak ilość pieniędzy gracz A, ile ma gracz B itd. Dlatego jestem prawie pewien, że chcesz trzymać się z dala od maszyn państwowych.
Ideą naszego frameworka 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, wypisz tę strukturę).
Użyliśmy wzorca poleceń, aby przedstawić wszystkie prawidłowe działania w grze, które gracz może wykonać. Oto przykładowa akcja:
Widzisz więc, że aby zdecydować, czy ruch jest prawidłowy, możesz skonstruować tę akcję, a następnie wywołać jej funkcję IsLegal, przekazując bieżący stan gry. Jeśli jest prawidłowy, a gracz potwierdzi działanie, możesz wywołać funkcję Zastosuj, aby faktycznie zmodyfikować stan gry. Upewniając się, że kod rozgrywki może modyfikować stan gry jedynie poprzez tworzenie i przesyłanie legalnych działań (czyli innymi słowy, rodzina metod Action :: Apply jest jedyną rzeczą, która bezpośrednio modyfikuje stan gry), wówczas zapewniasz, że Twoja gra stan nigdy nie będzie nieważny. Ponadto, używając wzorca poleceń, umożliwiasz serializację żądanych ruchów gracza i wysyłanie ich przez sieć w celu wykonania w stanach gry innego gracza.
Skończyło się na tym, że z tym systemem był jeden problem, który okazał się mieć dość eleganckie rozwiązanie. Czasami akcje miały dwie lub więcej faz. Na przykład gracz może wylądować na nieruchomości w Monopoly i musi teraz podjąć nową decyzję. Jaki jest stan gry między momentem rzutu kostką przez gracza, a podjęciem decyzji o zakupie nieruchomości? Poradziliśmy sobie z takimi sytuacjami, udostępniając członka „kontekstu akcji” naszego stanu gry. Kontekst akcji byłby normalnie pusty, co oznacza, że gra nie jest obecnie w żadnym specjalnym stanie. Kiedy gracz rzuca kośćmi, a akcja rzucania kośćmi zostaje zastosowana do stanu gry, zda sobie sprawę, że gracz wylądował na nienależącej do niego nieruchomości i może utworzyć nową właściwość „PlayerDecideToPurchaseProperty” kontekst akcji zawierający indeks gracza, na którego decyzję czekamy. Do czasu zakończenia akcji RollDice stan naszej gry oznacza, że aktualnie czeka, aż określony gracz zdecyduje, czy kupić nieruchomość, nie. Teraz metoda IsLegal wszystkich innych akcji może zwracać fałsz, z wyjątkiem działań „BuyProperty” i „PassPropertyPurchaseOpportunity”, które są legalne tylko wtedy, gdy stan gry ma kontekst akcji „PlayerDecideToPurchaseProperty”.
Dzięki wykorzystaniu kontekstów akcji nie ma ani jednego punktu w czasie życia gry planszowej, w którym struktura stanu gry nie odzwierciedla DOKŁADNIE tego, co dzieje się w grze w danym momencie. Jest to bardzo pożądana właściwość Twojego systemu gier planszowych. Znacznie łatwiej będzie ci pisać kod, jeśli możesz znaleźć 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 działania przez sieć do komputera głównego, który może zastosować akcję do „oficjalnego” stanu gry hosta, a następnie powtórzyć tę akcję z powrotem do wszystkich innych klientów, aby niech zastosują ją do swoich replikowanych stanów gry.
Mam nadzieję, że to było zwięzłe i pomocne.
źródło
Podstawowa struktura twojego silnika gry korzysta z wzorca stanu . Elementy w Twoim pudełku z grą to pojedyncze elementy różnych klas. Struktura każdego stanu może wykorzystywać wzorzec strategii lub metodę szablonową .
Fabryczne służy do tworzenia graczy, które są wkładane do listy graczy, innym Singleton. GUI będzie obserwować silnik gry, używając wzorca Observer i wchodzić z nim w interakcję, używając jednego z kilku obiektów polecenia utworzonych za pomocą wzorca poleceń . Korzystanie z Observer i Command może być używane w kontekście widoku pasywnego, ale w zależności od twoich preferencji można użyć prawie każdego wzorca MVP / MVC. Podczas zapisywania gry musisz złapać pamiątkę z jej aktualnego stanu
Zalecam przejrzenie niektórych wzorców na tej stronie i sprawdzenie, czy któryś z nich jest punktem wyjścia. Znowu sercem twojej planszy będzie automat stanowy. Większość gier będzie reprezentowana przez dwa stany przed grą / konfiguracją i samą grę. Ale możesz mieć więcej stanów, jeśli modelowana gra ma kilka różnych trybów gry. Stany nie muszą być sekwencyjne, na przykład gra wojenna Axis & Battles ma planszę bitewną, której gracze mogą używać do rozstrzygania bitew. Są więc trzy stany przed grą, plansza główna, plansza bitewna, przy czym gra nieustannie przełącza się między planszą główną i planszą bitwy. Oczywiście sekwencja tur może być również reprezentowana przez maszynę stanów.
źródło
Właśnie skończyłem projektowanie i wdrażanie gry opartej na stanach z wykorzystaniem polimorfizmu.
Korzystanie z podstawowej klasy abstrakcyjnej o nazwie,
GamePhase
która ma jedną ważną metodęOznacza to, że każdy
GamePhase
obiekt przechowuje aktualny stan gry, a wywołanieturn()
patrzy na jego aktualny stan i zwraca następnyGamePhase
.Każdy beton
GamePhase
ma konstruktorów, które przechowują cały stan gry. Każdaturn()
metoda zawiera trochę reguł gry. Chociaż rozpowszechnia to reguły, utrzymuje powiązane zasady blisko siebie. Końcowym rezultatem każdego z nichturn()
jest po prostu utworzenie następnejGamePhase
i przejście w pełnym stanie do następnej fazy.Pozwala
turn()
to być bardzo elastycznym. W zależności od twojej gry, dany stan może rozgałęziać się do wielu różnych typów faz. Tworzy to wykres wszystkich faz gry.Na najwyższym poziomie kod do kierowania jest bardzo prosty:
Jest to niezwykle przydatne, ponieważ mogę teraz łatwo stworzyć dowolny stan / fazę gry do testów
A teraz odpowiedz na drugą część twojego pytania, jak to działa w trybie dla wielu graczy? W niektórych przypadkach,
GamePhase
które wymagają interwencji użytkownika, wywołanie zturn()
zapytałoby o bieżącyPlayer
ichStrategy
stan / fazę.Strategy
to tylko interfejs wszystkich możliwych decyzji, jakiePlayer
można podjąć. Ta konfiguracja umożliwia równieżStrategy
na implementację z AI!Andrew Top powiedział również:
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. Aby poradzić sobie z jego przykładem, wystarczyłoby to jako parametr całkowity dla konstruktorów mojego betonu
GamePhase
s.Monopol
Przykładem
GamePhase
może być:Niektóre stany w bazie
GamePhase
to:A potem niektóre fazy zapisywałyby swój własny stan w razie potrzeby, na przykład PlayerRolls zapisywałoby, ile razy gracz wykonał kolejne dublety. Gdy opuścimy fazę PlayerRolls, nie przejmujemy się już kolejnymi rzutami.
Wiele faz można ponownie wykorzystać i połączyć. Na przykład
GamePhase
CommunityChestAdvanceToGo
utworzy następną fazęPlayerLandsOnGo
z obecnym stanem i zwróci ją. W konstruktorzePlayerLandsOnGo
obecnego gracza zostanie przeniesiony do Go, a jego pieniądze zostaną zwiększone o 200 $.źródło
Oczywiście istnieje wiele, wiele, wiele, wiele, wiele, wiele, wiele zasobów dotyczących tego tematu. Ale myślę, że jesteś na dobrej drodze, dzieląc obiekty i pozwalając im obsługiwać własne zdarzenia / dane i tak dalej.
Podczas tworzenia gier planszowych opartych na kafelkach dobrze jest mieć procedury mapowania między tablicą planszową a wierszem / kolumną iz powrotem, wraz z innymi funkcjami. Pamiętam moją pierwszą grę planszową (dawno temu), kiedy zmagałem się z tym, jak uzyskać wiersz / kolumnę z boardarray 5.
Nostalgia. ;)
W każdym razie http://www.gamedev.net/ to dobre miejsce na informacje. http://www.gamedev.net/reference/
źródło
Wiele materiałów, które mogę znaleźć w Internecie, to listy opublikowanych bibliografii. Sekcja publikacji Game Design Patterns zawiera linki do wersji PDF artykułów i rozpraw. Wiele z nich wygląda jak artykuły naukowe, takie jak wzorce projektowe dla gier . Istnieje również co najmniej jedna książka dostępna w Amazon, Patterns in Game Design .
źródło
Three Rings oferuje biblioteki Java na licencji LGPL. Nenya i Vilya to biblioteki związane z grami.
Oczywiście pomocne byłoby, gdyby Twoje pytanie dotyczyło platformy i / lub ograniczeń językowych, które możesz mieć.
źródło
Zgadzam się z odpowiedzią Pyrolisticsa i wolę jego sposób robienia rzeczy (chociaż przejrzałem tylko inne odpowiedzi).
Przypadkowo użyłem też jego nazewnictwa „GamePhase”. Zasadniczo to, co zrobiłbym w przypadku gry planszowej z turami, to aby twoja klasa GameState zawierała obiekt abstrakcyjnej GamePhase, o której wspomniał Pyrolistics.
Powiedzmy, że stany gry to:
Możesz mieć konkretne klasy pochodne dla każdego stanu. Posiadaj funkcje wirtualne przynajmniej dla:
W funkcji StartPhase () można ustawić wszystkie początkowe wartości stanu, na przykład wyłączenie wejścia innego gracza i tak dalej.
Gdy wywoływana jest funkcja roll.EndPhase (), upewnij się, że wskaźnik GamePhase jest ustawiony na następny stan.
W tej MovePhase :: StartPhase () możesz na przykład ustawić pozostałe ruchy aktywnego gracza na liczbę wyrzuconą w poprzedniej fazie.
Dzięki temu projektowi możesz rozwiązać problem „3 x podwójne = więzienie” w fazie rzutu. Klasa RollPhase może obsługiwać własny stan. Na przykład
Różnię się od pirolistyki tym, że powinna istnieć faza na wszystko, łącznie z sytuacją, gdy gracz wyląduje w skrzynce społeczności lub coś w tym rodzaju. Zajmę się tym wszystkim w 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 jest faza, w której gracz może TYLKO kupować nieruchomości, a następnie TYLKO hotele, a następnie TYLKO domy, to tak, jakby nie było wolności. Po prostu zatrzaśnij wszystkie te części w jedną Fazę Kupna i daj graczowi swobodę kupowania wszystkiego, czego chce. Klasa BuyPhase z łatwością radzi sobie z legalnymi zakupami.
Na koniec zajmijmy się planszą. Chociaż tablica 2D jest w porządku, polecam mieć wykres kafelkowy (gdzie kafelek jest pozycją na planszy). W przypadku monopolii byłaby to raczej lista podwójnie połączona. Wtedy każda płytka miałaby:
Byłoby więc znacznie łatwiej zrobić coś takiego:
Funkcja AdvanceTo może obsłużyć Twoje animacje krok po kroku lub cokolwiek chcesz. A także oczywiście zmniejszaj pozostałe ruchy.
Porady RS Conley dotyczące wzorca obserwatorów dla GUI są dobre.
Nie publikowałem dużo wcześniej. Mam nadzieję, że to komuś pomoże.
źródło
Jeśli Twoje pytanie nie dotyczy języka lub platformy. wtedy zalecałbym rozważenie AOP Patterns for State, Memento, Command itp.
Jaka jest odpowiedź .NET na AOP ???
Spróbuj też znaleźć fajne strony internetowe, takie jak http://www.chessbin.com
źródło