纸牌游戏设计

时间:2011-07-06 18:24:14

标签: class-design playing-cards

我刚才有一个与设计模式有关的小问题。

考虑可以容纳Card对象的Player对象。

Player player1;
Player player2;
Player dealer;

玩家可以互相给卡。是否有一种OO方法来设计处理此问题的方法?

player1.giveCard(Card, player2);

玩家1可以利用其他玩家的方法似乎不对。有什么想法吗?例如,是否所有玩家都有一个名为getCard的方法?

Player::giveCard(Card card, Player player) {
player.getCard(card)

}

3 个答案:

答案 0 :(得分:1)

面向对象方法的美妙之处在于,有无限的方法来抽象和设计相同的概念。问题是您想要选择的。

似乎在这个特定的实例中,您对所描述的抽象的关注是player1需要使用player2的方法来实现目标。但是,我想改变你对方法的看法。

首先,方法应该被认为是我们与世界的公共接口。另一方面,属性是我们保密并隐藏在对象/头部/身体/等内的私人物品。

确实,许多编程语言都有私有和受保护的方法,仅供内部使用,但这只是为了清理代码,因此我们的公共方法不会长达数百行。

对于您的纸牌游戏,玩家可以通过任何其他对象获得卡片,因此我将定义公共方法Player.giveCard(卡片)。这是一个简单的部分,已经被其他答案所触及。

但问题是,这个方法会发生什么? 另外,如何将卡从原始手中移除?

我们可以在这里做几件事,这个清单并不完整:

  1. 玩家可以与其他玩家进行互动。
  2. 在这种情况下,player1选择将card_589提供给player2。因此,player1调用方法player2.giveCard(card_589)。在现实世界中,这将由玩家1物理地拿出卡片供玩家2拿走来证明。如果player2接受该牌,则player1不再拥有该牌,并且必须将其从手中移除。如果player2不接受它,那么player1不会丢失该卡,并将其放回手中。

    为了对此进行建模,我们将制定一个简单的规则:giveCard方法返回一个布尔结果来指示player2是否接受了卡片....在player1调用player2.giveCard()之后,他对于player2是否接受了卡,因为这取决于播放器2,在这种情况下。

    我们的代码在player1的函数中可能看起来像这样:

    //begin pseudocode to give card to player identified by player2
    //let self refer to player1's own methods
    Player{
    public giveCardToPlayer( player2, card_id ){
        card = self.removeCardFromHand( card_id ); 
        cardTaken = player2.giveCard( card );
        if( cardTaken === false ){
            self.addCardToHand( card );
        }
    }
    //in the game management system, or elsewhere in player1's code, you then write
    player1.giveCardToPlayer( player2, card_587 );
    //or, if in another method "player1.play()", for example:
    //self.giveCardToPlayer( player2, card_587 )
    //
    //end pseudocode
    

    这是最简单的解决方案。在这里,player1没有看到player2决定是否获得card1。 Player1选择在他交出牌之前将牌从他自己的牌中取出,这样牌就不会同时出现在两个位置。如果Player2没有拿到卡,则player1将其放回到他的牌组中,否则什么都不做,因为该卡现在与player2一起。

    就个人而言,这是抽象模型的最简单方法,也是我最喜欢的。

    1. 玩家可以通过某个中间人进行互动
    2. 这是我最喜欢的场景,当我们对具有某种延迟的游戏进行建模时,例如在计算机网络模拟或国际象棋邮件模拟中。 Player1将卡邮寄给player2,但是player2可能会或可能不会收到该卡。在这个游戏中,让我们假设你有一张桌子,就像扑克桌一样,任何玩家都可以在自己和另一个玩家之间放一张牌,这样只有其他玩家可以到达该牌。

      对于这种情况,我们将创建一个名为Table的新对象,虽然有很多方法可以选择抽象将卡放在桌面上,但我会选择此方法作为动作的公共可用界面:

      Table.placeCardForUser( card, userId, myId, securityToken ) : bool
      Table.countCardsOnTableToUserFromUser( userId, myId, securityToken ) : int
      Table.pickUpCardToUser( userId, myId, securityToken ) : Card[0..*]
      Table.pickUpCardsToMe( myId, securityToken ) : Card[0..*]
      

      这引入了安全问题,因为我告诉Table,只有userId可以拿起卡,只有myId可以验证和检索卡,所以Table对象需要一些方法来验证我("当前对象")有权访问由" userId"标识的表格上的位置。和#34; myId",但也有很多解决方案。

      //begin psuedocode for current example
      //we are in player1's function body
      card = self.removeCardFromHand( card_587 );
      player2_id = self.identifyPlayerToReceive( card );
      table.placeCardForUser( card, player2_id, myId, securityToken );
      //end current action
      
      //at next opportunity to act, check to see
      //if card was taken
      cardCount = table.countCardsOnTableToUserFromUser( userId, myId, securityToken );
      if( cardCount > 1 ){
      //player2 has not taken card or other cards that have accumulated
          pickUpCards = self.decideToPickUpCardsToPlayer( player2_id );
          if( pickUpCards === true ){
              cards = table.pickUpCardToUser( player2_id, myId, securityToken );
              foreach( cards as card ){
                  self.addToHand( card );
              }
          }
      }
      //now check to see if anyone has given me cards between last round and this round
      cards = table.pickUpCardsToMe( myId, securityToken );
      foreach( cards as card ){
           //let us assume that player1 takes all cards given to him
           self.addToHand( card );
      }
      

      可以做出这种变化。你可以想象一下player1和player2之间的隧道。 Player1通过识别他目前没有办法将牌给玩家2来建立隧道,因此他创建了一个隧道。他给了player2一个隧道的副本,拿着"另一个结束"然后播放器2也保留了隧道的副本。就像桌子的情况一样,这个隧道现在是一个可以保持来回传递给玩家2的物品的地方,但由于只有玩家1和玩家2有链接或指针到隧道,只有这两个玩家可以把物品放入因此,我们有一个不需要那么多安全性的中间人。我们可以创建隧道以将所有玩家与所有其他玩家联系起来,这仍然是中介设计的变体。

      1. 自我识别卡
      2. 有时,我们希望设计更容易编码而不像现实。如果Player对象的代码忘记从手中移除卡对象会发生什么?现在,因为对象通常是通过引用传递的,所以player2和player1都有对卡的引用,游戏模拟认为同一张卡有两个副本!

        在这种情况下,我们可以将卡设计为自我感知,并让卡可以访问玩家的手。

        对于这种抽象,我将对卡片进行建模:

        //begin pseudocode
        Card{
             private owner; 
             //this is a private link to the object in which the card lies
             //we will allow any object to be the owner of the card, as long
             //as the object implements the "CardOwner" interface.
        
             public putInto( newOwner ){
             //whoever takes the card must specify a newOwner, which will
             //implement the "CardHolder" interface.
                   success = newOwner.addCard( self ); 
                   if( success ){
                       self.owner.removeCard( self );
                       self.owner = newOwner;
                   }
             }
        }
        

        然后我们可以按如下方式定义界面:

        //begin psuedocode
        iCardHolder{
            public removeCard( card ) : bool
            public addCard( card ) : bool
        }
        

        在这种情况下,我们已经脱离了现实"通过赋予卡本身执行操作的能力。但是,这在大型项目中是有用的,在这些大型项目中,您无法相信其他程序员能够记住有关如何正确处理卡的详细信息。

        通过让卡片控制谁有指针,我们可以确保任何时候只有一张卡片存在,无论谁使用它。

        现在,player1的代码可能如下所示:

         //give card to player2
         card = self.chooseCard();
         player2.giveCard( card );
        
         //put card on the floor
         card = self.chooseCard();
         floor.giveCard( card );
        
         //put card on the table
         card = self.chooseCard();
         table.giveCard( card );
        

        在每个对象中,我们都可以自由地改变接收卡片的方式以及保存卡片的位置。

        //player2 - is a simple CardHolder
        public function giveCard( card ){
             myHand = self;
             card.putInto( myHand );
        }
        
        //the dealer is a cheat and does not implement CardHolder, 
        //but he has three locations that can act as CardHoldes
        //they are:
        //  dealer.sleave, dealer.hand, and dealer.pocket
        public function giveCard( card ){
             location = self.chooseCardOwner( [ sleeve, hand, pocket ] );
             card.putInto( location );
        }
        
        //the floor has random piles that are accumulating
        public function giveCard( card ){
            pile = self.chooseRandomPile();
            card.putInto( pile );
        }
        

        这个选项很奇怪,但它给了我们很大的灵活性。在上面的示例中,Dealer和Floor甚至不是iCardHolder接口的实现者,但是他们持有对实现该接口的对象的引用,因此他们仍然可以使用该卡。

        在使用iCardHolder的每个实现中,这与其他实现完全不同,代码非常简单,因为我们已经卸载了卡位置的操作并将这个责任放在卡本身上,并且所有卡都关心是与它交互的对象,同意各种合同并承诺实现一个removeCard方法和一个addCard方法。作为安全性,卡片会将当前所有者的副本保存在自己的内存中,这样如果其中一个CardHolders出现错误,则卡片本身会保留其当前所有者的答案。

        长话短说

        没有一种正确的方法来模拟你的游戏。这完全取决于个人偏好以及您希望系统如何表现。成为一名程序员是件好事。作为进行代码设计的人,您可以设置程序运行的规则,什么是良好的对象交互,以及什么是不良的对象交互。

答案 1 :(得分:0)

你需要知道这张卡来自哪个播放器吗?

另外,我假设一个名为CardGame的对象将成为所有Player对象的持有者,并且会对玩家进行所有调用,使得一个Player对象不会改变另一个Player对象的状态,但是CardGame对象完成所有这些。

因此,在CardGame对象中,您可以进行如下调用:

player1.giveCard(Card); player2.receiveCard(卡);

答案 2 :(得分:0)

我会说玩家有一手牌,而Hand.addCard(牌)和Hand.removeCard(牌)就是在这里实施的方法。

游戏将处理从一个玩家移除一张牌并将其交给下一个(通过那些手牌),因为游戏知道所有玩家对象,但玩家对象不一定需要彼此了解。 / p>