Enige patronen voor het modelleren van bordspellen?

Voor de lol probeer ik een van de favoriete bordspellen van mijn zoon als software te schrijven. Uiteindelijk verwacht ik er een WPF UI bovenop te bouwen, maar op dit moment ben ik de machine aan het bouwen die de spellen en zijn regels modelleert.

Terwijl ik dit doe, blijf ik problemen tegenkomen die volgens mij veel voorkomen bij veel bordspellen, en misschien hebben anderen ze al beter opgelost dan ik.

(Merk op dat AI om het spel te spelen, en patronen rond hoge prestaties zijn niet interessant voor mij.)

Tot nu toe zijn mijn patronen:

  • Verschillende onveranderlijke typen die entiteiten in de speldoos vertegenwoordigen, b.v. dobbelstenen, schijven, kaarten, een bord, spaties op het bord, geld, enz.

  • Een object voor elke speler, dat de spelersbronnen bevat (bijv. geld, score), hun naam, enz.

  • Een object dat de staat van het spel weergeeft: de spelers, wie aan de beurt is, de indeling van de stukken op het bord, enz.

  • Een toestandsmachine die de beurtvolgorde beheert. Veel games hebben bijvoorbeeld een kleine pre-game waarbij elke speler gooit om te zien wie er als eerste mag; dat is de startstatus. Wanneer de beurt van een speler begint, rollen ze eerst, dan bewegen ze, dan moeten ze op hun plaats dansen, dan raden andere spelers welk kippenras ze zijn, dan krijgen ze punten.

Is er enige stand van de techniek waar ik gebruik van kan maken?

EDIT:Een ding dat ik me onlangs realiseerde, is dat de gamestatus in twee categorieën kan worden opgesplitst:

  • Spelartefactstatus. “Ik heb $ 10” of “mijn linkerhand is op blauw”.

  • Spelvolgordestatus. “Ik heb twee keer dubbel gegooid; de volgende zet me in de gevangenis”. Een staatsmachine kan hier zinvol zijn.

BEWERKEN:Waar ik echt naar op zoek ben, is de bestemanier om turn-based multiplayer-spellen zoals schaken of Scrabble of Monopoly te implementeren. Ik weet zeker dat ik zo’n spel zou kunnen maken door er gewoon van begin tot eind doorheen te werken, maar net als bij andere Design Patterns zijn er waarschijnlijk enkele manieren om dingen veel soepeler te laten verlopen die niet duidelijk zijn zonder zorgvuldige studie. Dat is waar ik op hoop.


Antwoord 1, autoriteit 100%

het lijkt erop dat dit een topic van 2 maanden oud is dat ik nu pas opmerk, maar wat maakt het uit. Ik heb eerder het gameplay-framework voor een commercieel netwerkbordspel ontworpen en ontwikkeld. We hadden een zeer prettige ervaring ermee te werken.

Uw spel kan waarschijnlijk in een (bijna) oneindig aantal toestanden zijn vanwege de permutaties van zaken als hoeveel geld speler A heeft, hoeveel geld speler B heeft, enzovoort… Daarom ben ik knap zeker dat je uit de buurt van staatsmachines wilt blijven.

Het idee achter ons raamwerk was om de gamestatus weer te geven als structuur met alle gegevensvelden die samen de volledige gamestatus opleveren (dwz: als je de game op schijf wilt opslaan, schrijf je die structuur weg).

We gebruikten het Opdrachtpatroonom alle geldige spelacties weer te geven die een speler zou kunnen maken. Hier is een voorbeeldactie:

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
};

Dus je ziet dat om te beslissen of een zet geldig is, je die actie kunt construeren en vervolgens zijn IsLegal-functie kunt aanroepen, waarbij je de huidige spelstatus doorgeeft. Als het geldig is en de speler de actie bevestigt, kun je de functie Toepassen aanroepen om de spelstatus daadwerkelijk te wijzigen. Door ervoor te zorgen dat je gameplay-code de gamestatus alleen kan wijzigen door juridische acties te maken en in te dienen (dus met andere woorden, de Action::Apply-familie van methoden is het enige dat de gamestatus rechtstreeks wijzigt), zorg je ervoor dat je game staat zal nooit ongeldig zijn. Door het commandopatroon te gebruiken, maakt u het bovendien mogelijk om de gewenste zetten van uw speler te serialiseren en deze over een netwerk te verzenden om te worden uitgevoerd op de spelstatussen van andere spelers.

Er was uiteindelijk één probleem met dit systeem dat een redelijk elegante oplossing bleek te hebben. Soms zouden acties twee of meer fasen hebben. De speler kan bijvoorbeeld op een eigendom in Monopoly landen en moet nu een nieuwe beslissing nemen. Wat is de staat van het spel tussen het moment waarop de speler de dobbelstenen gooit en voordat hij besluit een eigendom te kopen of niet? We hebben dit soort situaties aangepakt door een “Actiecontext”-lid van onze gamestatus te gebruiken. De actiecontext is normaal gesproken nul, wat aangeeft dat de game zich momenteel niet in een speciale staat bevindt. Wanneer de speler de dobbelstenen gooit en de actie voor het gooien van de dobbelstenen wordt toegepast op de spelstatus, realiseert hij zich dat de speler op een eigendom is beland dat geen eigendom is en kan hij een nieuwe actiecontext “PlayerDecideToPurchaseProperty” maken die de index van de speler bevat we wachten op een beslissing van. Tegen de tijd dat de RollDice-actie is voltooid, geeft onze spelstatus aan dat het momenteel wacht op de opgegeven speler om te beslissen of hij een eigendom wil kopen. Het is nu gemakkelijk voor de IsLegal-methode van alle andere acties om false te retourneren, behalve de acties “BuyProperty” en “PassPropertyPurchaseOpportunity”, die alleen legaal zijn als de spelstatus de actiecontext “PlayerDecideToPurchaseProperty” heeft.

Door het gebruik van actiecontexten is er nooit een enkel punt in de levensduur van het bordspel waarop de spelstatusstructuur niet PRECIES representeert wat er op dat moment in het spel gebeurt. Dit is een zeer wenselijke eigenschap van uw bordspelsysteem. Het zal veel gemakkelijker voor je zijn om code te schrijven als je alles kunt vinden wat je ooit wilt weten over wat er in het spel gebeurt door slechts één structuur te onderzoeken.

Bovendien strekt het zich heel goed uit tot netwerkomgevingen, waar clients hun acties via een netwerk kunnen indienen bij een hostmachine, die de actie kan toepassen op de “officiële” gamestatus van de host, en die actie vervolgens terug kan echoën naar alle andere klanten om ze het toe te passen op hun gerepliceerde spelstatussen.

Ik hoop dat dit beknopt en nuttig was.


Antwoord 2, autoriteit 16%

De basisstructuur van je game-engine gebruikt het State Pattern. De items van je gamebox zijn eenpersoonsbeddenvan verschillende klassen. De structuur van elke staat kan gebruik maken van Strategiepatroonof de sjabloonmethode.

Een Factorywordt gebruikt om de spelers te maken die in een lijst met spelers worden ingevoegd, een andere eenling. De GUI houdt de Game Engine in de gaten met behulp van het Observer-patroonen werkt hiermee samen door een van verschillende Command-objecten gemaakt met behulp van het Command Pattern. Het gebruik van Observer en Command kan worden gebruikt in de context van een Passive Viewongeveer elk MVP/MVC-patroon kan worden gebruikt, afhankelijk van uw voorkeuren. Wanneer je het spel opslaat, moet je een aandenkenvan de huidige staat

pakken

Ik raad je aan om enkele patronen op deze sitete bekijken en te kijken of ze je grijpen als uitgangspunt. Nogmaals, het hart van je speelbord wordt een staatsmachine. De meeste spellen worden vertegenwoordigd door twee statussen pre-game/setup en het eigenlijke spel. Maar je kunt meer toestanden hebben als de game die je aan het modelleren bent, verschillende spelmodi heeft. Staten hoeven niet opeenvolgend te zijn, bijvoorbeeld de wargame Axis & Battles heeft een gevechtsbord dat de spelers kunnen gebruiken om gevechten op te lossen. Er zijn dus drie toestanden voor het spel, het hoofdbord en het gevechtsbord, waarbij het spel voortdurend wisselt tussen het hoofdbord en het gevechtsbord. Natuurlijk kan de beurtvolgorde ook worden weergegeven door een toestandsmachine.


Antwoord 3, autoriteit 14%

Ik ben net klaar met het ontwerpen en implementeren van een state-based game met polymorfisme.

Een abstracte basisklasse gebruiken genaamd GamePhasedie één belangrijke methode heeft

abstract public GamePhase turn();

Wat dit betekent is dat elk GamePhase-object de huidige staat van het spel bevat, en een aanroep tot turn()kijkt naar de huidige staat en retourneert de volgende GamePhase.

Elke concrete GamePhaseheeft constructors die de helegamestatus bevatten. Elke turn()-methode bevat een klein beetje van de spelregels. Hoewel dit de regels verspreidt, houdt het gerelateerde regels dicht bij elkaar. Het eindresultaat van elke turn()is gewoon het creëren van de volgende GamePhaseen het in volledige staat doorgeven aan de volgende fase.

Hierdoor kan turn()zeer flexibel zijn. Afhankelijk van je spel kan een bepaalde staat vertakken naar veel verschillende soorten fasen. Dit vormt een grafiek van alle spelfasen.

Op het hoogste niveau is de code om het aan te sturen heel eenvoudig:

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

Dit is buitengewoon handig omdat ik nu gemakkelijk elke staat/fase van het spel kan maken om te testen

Om het tweede deel van je vraag te beantwoorden, hoe werkt dit in multiplayer? Binnen bepaalde GamePhases die gebruikersinvoer vereisen, zou een oproep van turn()de huidige Playerhun Strategyvragen gegeven de huidige toestand/fase. Strategyis slechts een interface van alle mogelijke beslissingen die een Playerkan nemen. Met deze opstelling kan ook Strategyworden geïmplementeerd met AI!

Ook Andrew Top zei:

Je spel kan waarschijnlijk in een (bijna) oneindig aantal toestanden zijn vanwege de permutaties van zaken als hoeveel geld speler A heeft, hoeveel geld speler B heeft, enz. Daarom ben ik er vrij zeker van dat je wilt om weg te blijven van staatsmachines.

Ik denk dat die verklaring erg misleidend is, hoewel het waar is dat er veel verschillende spelstatussen zijn, zijn er maar een paar spelfasen. Om zijn voorbeeld aan te pakken, zou het alleen een integer-parameter zijn voor de constructeurs van mijn concrete GamePhases.

Monopolie

Voorbeeld van enkele GamePhases zou zijn:

  • GameStart
  • PlayerRolls
  • PlayerLandsOnProperty (FreeParking, GoToJail, Go, enz.)
  • PlayerTrades
  • PlayerPurchasesProperty
  • PlayerPurchasesHouses
  • PlayerPurchasesHotels
  • PlayerPaysRent
  • PlayerFaillissementen
  • (Alle Kans- en Algemeen Fonds-kaarten)

En sommige staten in de basis GamePhasezijn:

  • Spelerslijst
  • Huidige speler (wie is aan de beurt)
  • Spelersgeld/eigendommen
  • Huizen/hotels op eigendommen
  • Spelerspositie

En dan zouden sommige fasen hun eigen status registreren als dat nodig is, bijvoorbeeld PlayerRolls zou het aantal keren registreren dat een speler opeenvolgende dubbels heeft gegooid. Zodra we de PlayerRolls-fase verlaten, geven we niet meer om opeenvolgende worpen.

Veel fasen kunnen worden hergebruikt en aan elkaar worden gekoppeld. De GamePhaseCommunityChestAdvanceToGozou bijvoorbeeld de volgende fase PlayerLandsOnGomaken met de huidige status en deze retourneren. In de constructor van PlayerLandsOnGozou de huidige speler naar Go worden verplaatst en zou hun geld worden verhoogd met $200.


Antwoord 4, autoriteit 8%

Natuurlijk zijn er vele, vele, vele, vele, vele, vele, vele bronnen over dit onderwerp.
Maar ik denk dat je op de goede weg bent door de objecten op te delen en ze hun eigen gebeurtenissen/gegevens enzovoort te laten verwerken.

Als je op tegels gebaseerde bordspellen doet, zul je het leuk vinden om routines te hebben om in kaart te brengen tussen de bordarray en rij/kolom en terug, naast andere functies. Ik herinner me mijn eerste bordspel (lang geleden) toen ik worstelde met het verkrijgen van row/col van boardarray 5.

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

Nostalgie. 😉

Hoe dan ook, http://www.gamedev.net/is een goede plek voor informatie.
http://www.gamedev.net/reference/


Antwoord 5, autoriteit 5%

Veel van het materiaal dat ik online kan vinden, zijn lijsten met gepubliceerde referenties. Het publicatiegedeelte van Game Design Patternsbevat links naar PDF-versies van de artikelen en scripties. Veel hiervan zien eruit als academische papers zoals Design Patterns for Games. Er is ook ten minste één boek verkrijgbaar bij Amazon, Patterns in Game Design.


Antwoord 6, autoriteit 3%

Three Ringsbiedt LGPL’s Java-bibliotheken. Nenya en Vilya zijn de bibliotheken voor game-gerelateerde dingen.

Natuurlijk zou het helpen als uw vraag melding maakte van platform- en/of taalbeperkingen die u mogelijk heeft.


Antwoord 7, autoriteit 3%

Ik ben het eens met het antwoord van Pyrolistical en ik geef de voorkeur aan zijn manier van doen (ik heb de andere antwoorden echter even doorgenomen).

Toevallig gebruikte ik ook zijn “GamePhase”-naamgeving. Wat ik in het geval van een turn-based bordspel zou doen, is dat je GameState-klasse een object bevat van de abstracte GamePhase zoals vermeld door Pyrolistical.

Laten we zeggen dat de spelstatussen zijn:

  1. Rollen
  2. Verplaatsen
  3. Kopen/Niet kopen
  4. Gevangenis

Je zou voor elke staat concrete afgeleide klassen kunnen hebben. Virtuele functies hebben voor ten minste:

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

In de StartPhase()-functie kunt u alle beginwaarden voor een status instellen, bijvoorbeeld de invoer van de andere speler uitschakelen, enzovoort.

Als roll.EndPhase() wordt aangeroepen, zorg er dan voor dat de GamePhase-aanwijzer in de volgende staat wordt gezet.

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

In deze MovePhase::StartPhase() stelt u bijvoorbeeld de resterende zetten van de actieve speler in op het in de vorige fase gegooide bedrag.

Met dit ontwerp op zijn plaats zou je je “3 x double = jail”-probleem in de Roll-fase kunnen oplossen. De klasse RollPhase kan zijn eigen status aan. Bijvoorbeeld

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.
}

Ik verschil met Pyrolistical dat er voor alles een fase moet zijn, ook wanneer de speler op de Community-kist of zoiets belandt. Ik zou dit allemaal in de MovePhase afhandelen. Dit komt omdat als je te veel opeenvolgende fasen hebt, de speler zich zeer waarschijnlijk te “geleid” zal voelen. Als er bijvoorbeeld een fase is waarin de speler ALLEEN eigendommen kan kopen en dan ALLEEN hotels en dan ALLEEN huizen, dan is het alsof er geen vrijheid is. Stop al die onderdelen gewoon in één BuyPhase en geef de speler de vrijheid om alles te kopen wat hij wil. De klasse BuyPhase kan gemakkelijk genoeg aan welke aankopen legaal zijn.

Laten we het tenslotte hebben over het speelbord. Hoewel een 2D-array prima is, raad ik aan om een ​​tegelgrafiek te hebben (waarbij een tegel een positie op het bord is). In het geval van monopolie zou het eerder een dubbel gekoppelde lijst zijn. Dan zou elke tegel een :

. hebben

  1. vorigeTile
  2. nextTile

Het zou dus veel gemakkelijker zijn om iets te doen als:

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

De functie AdvanceTo kan uw stapsgewijze animaties of wat u maar wilt, aan. En natuurlijk ook de resterende zetten verlagen.

RS Conley’s advies over waarnemerspatroon voor de GUI is goed.

Ik heb nog niet veel gepost. Ik hoop dat dit iemand helpt.


Antwoord 8, autoriteit 2%

Is er enige stand van de techniek waar ik gebruik van kan maken?

Als je vraag niet taal- of platformspecifiek is. dan raad ik je aan om AOP-patronen te overwegen voor staat, aandenken, commando, enz.

Wat is het .NET-antwoord op AOP???

Probeer ook een paar coole websites te vinden, zoals http://www.chessbin.com

Other episodes