2

我只是有一个与设计模式有关的小问题。

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

Player player1;
Player player2;
Player dealer;

玩家可以互相赠送卡片。是否有一种面向对象的方法来设计处理此问题的方法?

player1.giveCard(Card, player2);

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

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

}
4

3 回答 3

1

面向对象方法的美妙之处在于,有无数种方法可以抽象和设计相同的概念。问题是你想选择哪个。

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

一般来说,方法首先应该被认为是我们与世界的公共接口。另一方面,属性是我们保密并锁定在我们的对象/头部/身体/等中的那些私人事物。

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

对于您的纸牌游戏,任何其他对象都可以为玩家提供一张卡片,因此我将定义公共方法 Player.giveCard(Card)。这是简单的部分,其他答案已经触及。

但是问题变成了,这个方法内部会发生什么?另外,这张牌是如何从原来的手牌中取出的?

我们可以在这里做几件事,这个列表绝不是完整的:

  1. 玩家可以只与其他玩家互动。

在这种情况下,玩家 1 选择将 card_589 给玩家 2。因此,player1 调用方法 player2.giveCard(card_589)。在现实世界中,这可以通过玩家 1 将卡片拿出来让玩家 2 拿走来证明。如果玩家 2 接受了这张牌,那么玩家 1 将不再拥有它,必须将其从手中移除。如果玩家 2 不接受,则玩家 1 不会松手,而是将其放回手中。

为了对此进行建模,我们将制定一个简单的规则:giveCard 方法返回一个布尔值结果来指示 player2 是否拿走这张牌……在 player1 调用 player2.giveCard() 之后,他对 player2 是否拿走这张牌没有发言权,因为在这种情况下,这取决于 player2。

我们的代码在 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

这是最简单的解决方案。在这里,玩家 1 在玩家 2 的决策中看不到任何关于卡片 1 是否被拿走的决定。玩家 1 选择在交出之前从自己手中取出卡片,这样卡片就不会同时出现在两个地方。如果玩家 2 没有拿走这张牌,玩家 1 就会把它放回他的牌库,否则什么都不做,因为这张牌现在和玩家 2 在一起。

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

  1. 玩家可以通过一些中介进行交互

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

对于这种情况,我们将创建一个名为 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”和“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 之间的隧道。玩家 1 通过识别他当前没有办法给玩家 2 卡片来建立隧道,因此他创建了一个隧道。他将隧道的副本交给玩家 2,拿着“另一端”,然后玩家 2 也保留隧道的副本。就像桌子的情况一样,这个隧道现在是一个可以存放来回传递给玩家 2 的物品的地方,但是因为只有玩家 1 和玩家 2 有指向隧道的链接或指针,所以只有这两个玩家可以将物品放入隧道或将它们取出,因此,我们有一个不需要太多安全性的中介。我们可以创建隧道将所有玩家与所有其他玩家联系起来,这仍然是中介设计的一种变体。

  1. 自我意识卡

有时,我们想要更容易编码且不太像现实的设计。如果 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 方法。作为安全措施,卡片会在自己的内存中保存当前所有者的副本,因此如果其中一位持卡人出现错误,卡片本身会将答案保存给当前所有者。

长话短说

没有一种正确的方法来为您的游戏建模。这实际上完全取决于个人偏好以及您希望系统如何运行。这就是成为程序员的美妙之处。作为进行代码设计的人,您可以设置程序如何运行的规则,以及什么是好的对象交互,什么是坏的对象交互。

于 2015-02-26T00:01:08.307 回答
0

您需要知道卡片来自哪个玩家吗?

另外,我假设一个名为 CardGame 的对象将是所有 Player 对象的持有者,并且将对玩家进行所有调用,这样一个 Player 对象不会改变另一个 Player 对象的状态,而是 CardGame 对象做这一切。

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

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

于 2011-07-06T18:40:27.807 回答
0

我会说 Player 有 Hand 并且 Hand.addCard(card) 和 Hand.removeCard(card) 将是这里实现的方法。

游戏将处理从一个玩家身上移除一张牌并将其交给下一个玩家(通过这些手牌调用),因为游戏知道所有玩家对象,但玩家对象不一定需要知道彼此。

于 2011-07-06T18:40:50.253 回答