模拟棋盘游戏有什么模式吗?

为了好玩,我试着把我儿子最喜欢的棋盘游戏之一写成一个软件。最终,我希望在它的基础上构建一个 WPF UI,但是现在我正在构建一个机器来模拟游戏及其规则。

当我这样做的时候,我不断看到一些问题,我认为这些问题在许多棋盘游戏中都很常见,也许其他人已经比我更好地解决了这些问题。

(注意人工智能玩的游戏,以及高性能的模式对我来说并不感兴趣。)

到目前为止,我的模式是:

  • 几种不可变的类型代表游戏框中的实体,例如骰子、跳棋、卡片、棋盘、棋盘上的空格、钱等。

  • 每个玩家的一个对象,其中包含玩家的资源(如金钱,分数) ,他们的名字等。

  • 代表游戏状态的物体: 玩家,轮到谁,棋子在棋盘上的布局等等。

  • 管理转向序列的状态机。例如,许多游戏都有一个小型的赛前游戏,每个玩家投掷,看看谁先走; 这是开始状态。当一个玩家的回合开始时,他们首先滚动,然后他们移动,然后他们必须在原地跳舞,然后其他玩家猜出他们是什么品种的鸡,然后他们得分。

有什么我可以利用的先进技术吗?

编辑: 最近我意识到游戏状态可以分为两类:

  • 游戏神器状态 。“我有10美元”或“我的左手是蓝色的”。

  • 游戏序列状态 。“我掷了两次双骰,下一次我就进监狱了。”。在这里,状态机可能是有意义的。

编辑: 我在这里真正寻找的是 最好的方式来实现多玩家回合游戏,如国际象棋、拼字游戏或大富翁。我确信我可以创建这样一个游戏,只是通过工作,通过它开始到结束,但是,像其他设计模式,可能有一些方法,使事情进行得更顺利,没有仔细研究是不明显的。这正是我所希望的。

23718 次浏览

Three Rings offers LGPL'd Java libraries. Nenya and Vilya are the libraries for game related stuff.

Of course, it would help if your question mentioned platform and/or language restrictions you might have.

Of course there are many, many, many, many, many, many, many, resources about this topic. But I think you are on the right path dividing up the objects and let them handle its own events/data and so on.

When doing tiled based board games you'll find it nice to have routines to map between the board array and row/col and back, along other features. I remember my first board game (long long ago) when I struggeled with how to get row/col from boardarray 5.

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

Nostalgy. ;)

Anyway, http://www.gamedev.net/ is a good place for information. http://www.gamedev.net/reference/

The basic structure of your game engine uses the State Pattern. The items of your game box are singletons of various classes. The structure of each state may use Strategy Pattern or the Template Method.

A Factory is used to create the players which are inserted into a list of players, another singleton. The GUI will keep watch on the Game Engine by using the Observer pattern and interact with this by using one of several Command object created using the Command Pattern. The use of Observer and Command can be used with in the context of a Passive View But just about any MVP/MVC pattern could be used depending on your preferences. When you save the game you need to grab a memento of it's current state

I recommending looking over some of the patterns on this site and see if any of them grab you as a starting point. Again the heart of your game board is going to be a state machine. Most games will represented by two states pre-game/setup and the actual game. But you can had more states if the game you are modelling has several distinct modes of play. States don't have to be sequential for example the wargame Axis & Battles has a battle board the players can use to resolve battles. So there are three states pre-game, main board, battle board with the game continually switching between the main board and battle board. Of course the turn sequence can also be represented by a state machine.

it seems this is a 2 month old thread that I've just noticed now, but what the heck. I've designed and developed the gameplay framework for a commercial, networked board game before. We had a very pleasant experience working with it.

Your game can probably be in a (close to) infinite amount of states because of the permutations of things like how much money player A has, how much money player B has, and etc... Therefore, I'm pretty sure you want to stay away from state machines.

The idea behind our framework was to represent the game state as structure with all the data fields that together, provide the complete game state (ie: if you wanted to save the game to disk, you write that structure out).

We used the Command Pattern to represent all of the valid game actions a player could make. Here would be an example action:

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

So you see that to decide whether a move is valid, you can construct that action and then call its IsLegal function, passing in the current game state. If it is valid, and the player confirms the action, you can call the Apply function to actually modify the game state. By ensuring that your gameplay code can only modify the game state by creating and submitting legal Actions (so in other words, the Action::Apply family of methods are the only thing that directly modifies the game state), then you ensure that your game state will never be invalid. Furthermore, by using the command pattern, you make it possible to serialize your player's desired moves and send them over a network to be executed on other player's game states.

There ended up being one gotcha with this system that turned out to have a fairly elegant solution. Sometimes actions would have two or more phases. For example, the player may land on a property in Monopoly and must now make a new decision. What is the game state between when the player rolled the dice, and before they decide to purchase a property or not? We managed situations like this by featuring an "Action Context" member of our game state. The action context would normally be null, indicating that the game is not currently in any special state. When the player rolls the dice and the dice rolling action is applied to the game state, it will realize that the player has landed on an un-owned property, and can create a new "PlayerDecideToPurchaseProperty" action context that contains the index of the player we are waiting for a decision from. By the time the RollDice action has completed, our game state represents that it is currently waiting for the specified player to decide whether to buy a property is not. It is now easy for all other actions' IsLegal method to return false, except for the "BuyProperty" and "PassPropertyPurchaseOpportunity" actions, which are only legal when the game state has the "PlayerDecideToPurchaseProperty" action context.

Through the use of action contexts, there is never a single point in the life-time of the board game where the game state structure does not completely represent EXACTLY what is happening in the game at that point in time. This is a very desirable property of your board game system. It will it much easier for you to write code when you can find everything you ever want to know about what's happening in the game by examining only one structure.

Furthermore, it extends very nicely to networked environments, where clients can submit their actions over a network to a host machine, which can apply the action to the host's "official" game state, and then echo that action back to all the other clients to have them apply it to their replicated game states.

I hope this was concise and helpful.

Much of the materials I can find online are lists of published references. The publications section of Game Design Patterns has links to PDF versions of the articles and theses. Many of these look like academic papers like Design Patterns for Games. There is also at least one book available from Amazon, Patterns in Game Design.

I just finished designing and implementing a state based game using polymorphism.

Using a base abstract class called GamePhase that has one important method

abstract public GamePhase turn();

What this means is every GamePhase object holds the current state of the game, and a call to turn() looks at its current state and returns the next GamePhase.

Each concrete GamePhase has constructors that hold the entire game state. Each turn() method has a little bit of the game rules inside them. While this spreads the rules around, it keeps related rules close together. The end result of each turn() is just creating the next GamePhase and passing in the full state into the next phase.

This allows turn() to be very flexible. Depending on your game a given state can branch to many different types of phases. This forms a graph of all game phases.

At the highest level the code to drive it is very simple:

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

This is extremely useful as I can now easily create any state/phase of the game for testing

Now to answer the second part of your question, how does this work in multiplayer? Within certain GamePhases that require user input, a call from turn() would ask the current Player their Strategy given the current state/phase. Strategy is just a interface of all the possible decisions a Player can make. This setup also allows Strategy to be implemented with AI!

Also Andrew Top said:

Your game can probably be in a (close to) infinite amount of states because of the permutations of things like how much money player A has, how much money player B has, and etc... Therefore, I'm pretty sure you want to stay away from state machines.

I think that statement is very misleading, while it is true that there are a lot of different game states, there are only a few game phases. To handle his example, all it would be is an integer parameter to the constructors of my concrete GamePhases.

Monopoly

Example of some GamePhases would be:

  • GameStarts
  • PlayerRolls
  • PlayerLandsOnProperty (FreeParking, GoToJail, Go, etc)
  • PlayerTrades
  • PlayerPurchasesProperty
  • PlayerPurchasesHouses
  • PlayerPurchasesHotels
  • PlayerPaysRent
  • PlayerBankrupts
  • (All Chance and Community Chest cards)

And some states in the base GamePhase are:

  • List of Players
  • Current Player (who's turn)
  • Player's Money/Property
  • Houses/Hotels on Properties
  • Player Position

And then some phases would record their own state as needed, for example PlayerRolls would record the number of times a player has roll consecutive doubles. Once we leave the PlayerRolls phase, we don't care about consecutive rolls anymore.

A lot of phases can be reused and linked together. For example the GamePhase CommunityChestAdvanceToGo would create the next phase PlayerLandsOnGo with the current state and return it. In the constructor of PlayerLandsOnGo the current player would be moved to Go and their money would be incremented by $200.

Is there some prior art I can take advantage of?

If your question ain't language- or platform-specific. then I would recommend that you consider AOP Patterns for State, Memento, Command, etc.

Whats the .NET answer to AOP???

Also try to find some cool websites such as http://www.chessbin.com

I agree with Pyrolistical's answer and I prefer his way of doing things (I just skimmed the other answers though).

Coincidentally I also used his "GamePhase" naming. Basically what I would do in the case of a turn-based board game is have your GameState class contain an object of the abstract GamePhase as mentioned by Pyrolistical.

Lets say the game states are:

  1. Roll
  2. Move
  3. Buy/Don't buy
  4. Jail

You could have concrete derived classes for each state. Have virtual functions at least for:

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

In the StartPhase() function you could set all the initial values for a state for example disabling the other player's input and so forth.

When roll.EndPhase() is called then make sure the GamePhase pointer gets set to the next state.

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

In this MovePhase::StartPhase() you would for example set the active player's remaining moves to the amount rolled in the previous phase.

Now with this design in place you could sort out your "3 x double = jail" problem inside the Roll phase. The RollPhase class can handle its own state. For example

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

I differ from Pyrolistical in that there should be a phase for everything including when the player lands on Community chest or something. I would handle this all in the MovePhase. This is because if you have too many sequential phases, the player will very likely feel too "guided". For example, if there is a phase where the player can ONLY buy properties and then ONLY buy hotels and then ONLY buy houses, its like there is no freedom. Just slam all those parts into one BuyPhase and give the player the freedom to buy anything he wants. The BuyPhase class can easily enough handle which purchases are legal.

Finally let's address the gameboard. Though a 2D array is fine I'd recommend having a tile graph (where a tile is a position on the board). In the case of monoppoly it would rather be a doubly-linked list. Then every tile would have a :

  1. previousTile
  2. nextTile

So it would be much easier to do something like:

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

The AdvanceTo function can handle your step by step animations or whatever you like. And also decrement remaining moves of course.

RS Conley's Advice on Observer Pattern for the GUI is good.

I've not posted much before. Hope this helps someone.