用于建模棋盘游戏的任何模式?

时间:2008-12-11 21:24:27

标签: design-patterns

为了好玩,我正在尝试将我儿子最喜欢的棋盘游戏作为一个软件编写。最终我希望在它上面构建一个WPF UI,但是现在我正在构建一个模拟游戏及其规则的机器。

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

(注意AI玩游戏,高性能模式对我来说并不感兴趣。)

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

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

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

  • 一个代表游戏状态的对象:玩家,转向的人,棋盘上的peices布局等等。

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

是否有一些我可以利用的现有技术?

编辑:我最近意识到的一件事是游戏状态可以分为两类:

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

  • 游戏序列状态。 “我已经两次翻过双打;下一次让我入狱”。状态机可能在这里有意义。

编辑:我真正想要的是最好的方式来实现像Chess或Scrabble或Monopoly这样的多人回合制游戏。我确信我可以通过从头开始完成来创建这样的游戏,但是,像其他设计模式一样,可能有一些方法可以让事情变得更加顺利,如果没有仔细研究就不会明显。这就是我所希望的。

8 个答案:

答案 0 :(得分:110)

这似乎是我刚才注意到的一个2个月大的帖子,但是到底是什么。我以前为商业网络棋盘游戏设计和开发了游戏框架。我们有一个非常愉快的经历。

你的游戏可能处于(接近)无限数量的状态,因为玩家A有多少钱,玩家B有多少钱等等等的排列...因此,我很漂亮确定你想远离国家机器。

我们框架背后的想法是将游戏状态表示为具有所有数据字段的结构,提供完整的游戏状态(即:如果您想将游戏保存到磁盘,则将该结构写出来)。 / p>

我们使用Command Pattern来表示玩家可以制作的所有有效游戏动作。这将是一个示例动作:

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

因此,您可以看到要确定移动是否有效,您可以构造该操作,然后调用其IsLegal函数,传入当前游戏状态。如果它有效,并且玩家确认了该动作,则可以调用Apply功能来实际修改游戏状态。通过确保您的游戏代码只能通过创建和提交合法操作来修改游戏状态(换句话说,Action :: Apply系列方法是唯一直接修改游戏状态的东西),那么您可以确保您的游戏国家永远不会无效。此外,通过使用命令模式,您可以序列化播放器所需的移动并通过网络发送,以便在其他玩家的游戏状态下执行。

最终成为这个系统的一个问题,结果证明它有一个相当优雅的解决方案。有时行动会有两个或更多阶段。例如,玩家可以登陆Monopoly的财产,现在必须做出新的决定。玩家掷骰子之间,以及决定购买房产之前的游戏状态是什么?我们通过展示游戏状态的“动作上下文”成员来管理这样的情况。动作上下文通常为空,表示游戏当前不处于任何特殊状态。当玩家掷骰子并且骰子滚动动作应用于游戏状态时,它将意识到玩家已经登陆了非拥有的财产,并且可以创建包含玩家索引的新“PlayerDecideToPurchaseProperty”动作上下文我们正等着做出决定。当RollDice动作完成时,我们的游戏状态表示它正在等待指定的玩家决定是否购买房产。除了“BuyProperty”和“PassPropertyPurchaseOpportunity”操作之外,所有其他操作'IsLegal方法现在很容易返回false,这些操作仅在游戏状态具有“PlayerDecideToPurchaseProperty”操作上下文时才合法。

通过使用动作上下文,在棋盘游戏的生命周期中从来没有一个点,游戏状态结构并不能完全代表游戏中那个时刻发生的事情。这是您的棋盘游戏系统非常理想的属性。当你通过只检查一个结构找到你想知道的关于游戏中发生的事情的一切时,编写代码会更容易。

此外,它可以很好地扩展到网络环境,客户可以通过网络将其操作提交给主机,主机可以将操作应用到主机的“官方”游戏状态,然后将该操作回复给所有其他客户将它们应用于复制的游戏状态。

我希望这简洁而有用。

答案 1 :(得分:18)

游戏引擎的基本结构使用State Pattern。您的游戏盒中的项目是各种类别的singletons。每个州的结构可以使用Strategy Patternthe Template Method

Factory用于创建插入玩家列表中的玩家,另一个单身人士。 GUI将使用Observer pattern监视游戏引擎,并使用Command Pattern创建的几个Command对象之一与之交互。 Observer和Command的使用可以在Passive View的上下文中使用但是根据您的偏好,几乎可以使用任何MVP / MVC模式。保存游戏时,您需要获取其当前状态的memento

我建议查看此site上的某些模式,看看是否有任何模式会将您作为起点。游戏板的核心将再次成为状态机。大多数游戏将由游戏前/设置前的两个州和实际游戏代表。但是如果您正在建模的游戏有几种不同的游戏模式,那么您可以获得更多状态。国家不必是连续的,例如战争游戏Axis&战斗有一个战斗板,玩家可以用来解决战斗。所以游戏前有三个状态,主板,战斗板与游戏在主板和战斗板之间不断切换。当然,转弯序列也可以由状态机表示。

答案 2 :(得分:14)

我刚刚完成了使用多态性设计和实现基于状态的游戏。

使用名为GamePhase的基本抽象类,它有一个重要的方法

abstract public GamePhase turn();

这意味着每个GamePhase对象都保持游戏的当前状态,并且对turn()的调用会查看其当前状态并返回下一个GamePhase

每个具体GamePhase都有包含整个游戏状态的构造函数。每个turn()方法都有一些游戏规则。虽然这会扩展规则,但它会使相关规则保持紧密。每个turn()的最终结果只是创建下一个GamePhase并将完整状态传递到下一个阶段。

这使turn()非常灵活。根据您的游戏,给定的州可以分支到许多不同类型的阶段。这形成了所有游戏阶段的图表。

在最高级别,驱动它的代码非常简单:

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

这非常有用,因为我现在可以轻松创建游戏的任何状态/阶段进行测试

现在回答你问题的第二部分,这在多人游戏中是如何运作的?在需要用户输入的某些GamePhase内,来自turn()的来电会在给定当前状态/阶段时询问当前Player他们的StrategyStrategy只是Player可以做出的所有可能决策的界面。此设置还允许使用AI实现Strategy

安德鲁·托普也说:

你的游戏可能处于(接近)无限数量的状态,因为玩家A有多少钱,玩家B有多少钱等等的排列......所以,我很确定你想要的远离国家机器。

我认为这种说法非常具有误导性,但确实存在很多不同的游戏状态,只有少数游戏阶段。为了处理他的例子,它只是我具体GamePhase的构造函数的整数参数。

专卖

一些GamePhase的例子是:

  • GameStarts
  • PlayerRolls
  • PlayerLandsOnProperty(FreeParking,GoToJail,Go等)
  • PlayerTrades
  • PlayerPurchasesProperty
  • PlayerPurchasesHouses
  • PlayerPurchasesHotels
  • PlayerPaysRent
  • PlayerBankrupts
  • (所有机会和公益金卡)

基地GamePhase中的一些州是:

  • 球员名单
  • 当前玩家(轮到谁)
  • 玩家的金钱/财产
  • 房屋/物业酒店
  • 球员位置

然后某些阶段会根据需要记录自己的状态,例如PlayerRolls会记录玩家连续掷双打的次数。一旦我们离开PlayerRolls阶段,我们就不再关心连续的滚动了。

许多阶段可以重复使用并链接在一起。例如,GamePhase CommunityChestAdvanceToGo将使用当前状态创建下一个阶段PlayerLandsOnGo并将其返回。在PlayerLandsOnGo的构造函数中,当前玩家将被转移到Go并且他们的钱将增加200美元。

答案 3 :(得分:8)

当然,关于这个主题有很多很多很多很多很多很多资源。 但是我认为你在正确的道路上划分对象并让它们处理自己的事件/数据等等。

当进行基于平铺的棋盘游戏时,你会发现在棋盘阵列和行/ col之间以及其他功能之间进行映射的例程很不错。我记得我的第一个棋盘游戏(很久很久以前),当我开始讨论如何从boardarray 5获得行/ col。

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

Nostalgy。 ;)

无论如何,http://www.gamedev.net/是一个获取信息的好地方。 http://www.gamedev.net/reference/

答案 4 :(得分:5)

我在网上找到的大部分资料都是已发表的参考资料清单。 Game Design Patterns的出版物部分链接到文章和论文的PDF版本。其中许多看起来像Design Patterns for Games等学术论文。亚马逊至少还有一本书,Patterns in Game Design

答案 5 :(得分:3)

Three Rings提供了LGPL的Java库。 Nenya和Vilya是游戏相关的图书馆。

当然,如果您的问题提到您可能有的平台和/或语言限制,那将会有所帮助。

答案 6 :(得分:2)

我同意Pyrolistical的回答,我更喜欢他的做事方式(我只是略过了其他答案)。

巧合的是,我也使用了他的“GamePhase”命名。基本上我会在回合制棋盘游戏的情况下做的是让你的GameState类包含Pyrolistical提到的抽象GamePhase的对象。

让我们说游戏状态是:

  1. 卷筒
  2. 移动
  3. 购买/不购买
  4. 监狱
  5. 您可以为每个州提供具体的派生类。至少具有虚函数:

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

    在StartPhase()函数中,您可以设置状态的所有初始值,例如禁用其他玩家的输入等等。

    调用roll.EndPhase()时,请确保将GamePhase指针设置为下一个状态。

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

    在此MovePhase :: StartPhase()中,您可以将活动玩家的剩余移动设置为上一阶段中滚动的金额。

    现在有了这个设计,你可以在Roll阶段中解决你的“3 x double = jail”问题。 RollPhase类可以处理自己的状态。例如

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

    我与Pyrolistical的不同之处在于,应该有一个阶段,包括当玩家登陆社区胸部或其他东西时。我会在MovePhase中处理这一切。这是因为如果你有太多的连续阶段,玩家很可能会觉得太“引导”。例如,如果有一个阶段,玩家只能购买房产,然后只购买酒店,然后只买房子,就像没有自由。只需将所有这些部件甩到一个BuyPhase中,然后让玩家自由购买任何他想要的东西。 BuyPhase类可以轻松地处理哪些购买是合法的。

    最后让我们来解决游戏板问题。虽然2D阵列很好但我建议使用平铺图(其中平铺是板上的位置)。在monoppoly的情况下,它宁愿是双重链表。然后每个瓷砖都有一个:

    1. previousTile
    2. nextTile
    3. 所以做这样的事情要容易得多:

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

      AdvanceTo功能可以处理您的分步动画或任何您喜欢的动画。当然,还会减少剩下的动作。

      RS Conley对GUI的观察者模式的建议很好。

      我以前没发过多少内容。希望这有助于某人。

答案 7 :(得分:1)

  

是否有一些我可以利用的现有技术?

如果您的问题不是语言或平台特定的。那么我建议你考虑状态,纪念,命令等的AOP模式。

.NET回答AOP是什么?

还尝试找到一些很酷的网站,例如http://www.chessbin.com