卡牌游戏设计

发布于 2024-11-18 22:02:15 字数 398 浏览 3 评论 0原文

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

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

Player player1;
Player player2;
Player dealer;

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

player1.giveCard(Card, player2);

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

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

}

I just had a small question related to design patterns.

Consider Player objects that can hold Card objects.

Player player1;
Player player2;
Player dealer;

Players can give cards to each other. Is there a OO way to design a method that handles this?

player1.giveCard(Card, player2);

It doesn't seem right that player1 could utilize another player's methods. Any thoughts? For instance, should all players have a method called getCard?

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

}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

得不到的就毁灭 2024-11-25 22:02:15

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

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

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

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

对于您的纸牌游戏,任何其他对象都可以向玩家提供一张纸牌,因此我将定义公共方法 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是否拿走牌没有发言权,因为在这种情况下,这取决于玩家 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

这是最简单的解决方案。在这里,玩家 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 );
}

可以对此进行变化。您可以想象玩家 1 和玩家 2 之间有一条隧道。玩家 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 );
}

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

在使用 iCardHolder 的每个实现中(与其他实现完全不同),代码非常简单,因为我们已经卸载了卡位置的操作并将该责任放在卡本身上,并且卡所关心的是与之交互的对象,同意某种契约并承诺实现一个removeCard方法和一个addCard方法。为了安全起见,卡在自己的内存中保留了当前所有者的副本,因此,如果持卡人之一出现错误,卡本身会保存当前所有者的答案。

长话短说

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

The beautiful thing about Object Oriented approaches is that there are infinite ways to abstract and design the same concept. The question is which you would like to choose.

It seems that in this particular instance, your concern with the abstraction that you have described is that player1 needs to use the methods of player2 to accomplish the goal. However, I'd like to change the way you think of methods.

Methods, in general, should be thought of, first, as our public interfaces with the world. Properties, on the other hand, are those private things that we keep secret and locked up inside our objects/heads/bodies/etc.

It is true, that many programming languages have private and protected methods that are intended for internal use only, but that is really just to clean up the code so our public methods are not hundreds of lines long.

For your card game, a Player can be given a card by any other Object, so I would define the public method Player.giveCard( Card ). This is the simple part, which has already been touched on by other answers.

But the question becomes, what happens inside this method?
Also, how does the Card get removed from the original hand?

We can do several things here, and this list is by no means complete:

  1. The player can interact just with the other player.

In this situation, player1 chooses to give card_589 to player2. So, player1 calls the method player2.giveCard( card_589 ). In the real world, this would be demonstrated by player1 physically holding the card out for player2 to take. If player2 accepts the card, player1 no longer has it and must remove it from his hand. If player2 does not accept it, then player1 does not loose the card, and puts it back in his hand.

To model this, we would make one simple rule: the giveCard method returns a boolean result to indicate whether player2 takes the card.... After player1 calls player2.giveCard(), he has no say in whether player2 takes the card, because that is up to player2, in this scenario.

Our code might look like this somewhere inside player1's functions:

//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

This is the simplest solution. Here, player1 does not see anything within player2's decision making as to whether card1 gets taken. Player1 chooses to remove the card from his own hand before he hands it over so the card is not in two places at once. If Player2 does not take the card, player1 puts it back in his deck, but otherwise does nothing, because the card is now with player2.

Personally, this is the simplest way to abstract the model, and my favorite.

  1. The player can interact through some intermediary

This is my favorite scenario when we are modeling a game that has some sort of delay such as in a computer network simulation, or a chess by mail simulation. Player1 mails the card to player2, but player2 may or may not receive the card. In this game, lets assume you have a table, like a poker table, and any player can put a card down between himself and another player so that only that other player can reach the card.

For this scenario, we would create a new object called Table, and while there are many ways that we can choose to abstract the placing of the card on the table, I will choose this method as the publicly available interface for the action:

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..*]

This introduces security issues, because I am telling the Table that only userId can pick up the card, and only myId can verify and retrieve the card, so the Table object needs some way to verify that I ("the current object") have the right to access the spot on the table identified by "userId" and "myId", but there are lots of solutions to this as well.

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

Variations of this can be made. You can imagine a tunnel between player1 and player2. Player1 establishes the tunnel by recognizing that he does not currently have a way to give cards to player2 and so he creates a tunnel. He gives a copy of the tunnel to player2, holding the "other end", and player2 then keeps a copy of the tunnel as well. Like the table situation, this tunnel now is a place that can keep items that are being passed back and forth to player2, but because only player1 and player2 have links, or pointers, to the tunnel, only these two players can put items in the tunnel or take them out, so therefore, we have an intermediary that does not require as much security. We can create tunnels to link all players with all other players, and this is still a variation of the intermediary design.

  1. The self aware card

Sometimes, we want designs that are easier to code and less like reality. What happens if the code for the Player object forgets to remove the card object from his hand? Now, because objects are generally passed by reference, player2 and player1 each have a reference to the card, and the game simulation thinks there are two copies of the same card!

In this situation, we can design the card to be self-aware, and give the card access to a player's hand.

For this abstraction, I will model the card as such:

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

We then can define the interface as follows:

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

In this scenario, we have divorced ourselves from "reality" by giving the card itself the ability to perform actions. But where this is useful is in large projects where you cannot trust the other programmers to remember the details about how the Card is properly handled.

By giving the card control over who has pointers to it, we can ensure that only one copy of the card exists at any time, no matter who is using it.

Now, player1's code might look like this:

 //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 );

And in each of these objects, we have the freedom to change the way we receive the card and where we keep it.

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

This option is bizarre, but it gives us a lot of flexibility. In the above example, the Dealer and the Floor are not even implementers of the iCardHolder interface, but they hold references to objects to that do implement that interface, so they can still take the card.

In each of these implementations using iCardHolder, which is completely different from the others, the code is incredibly simple, because we have offloaded the manipulation of the cards location and put that responsibility on the card itself, and all the card cares about is that the objects that interact with it, agree to a contract of sorts and promise to implement one removeCard method, and one addCard method. As security, the card keeps a copy of the current owner in its own memory, so that if there is a mistake by one of the CardHolders, the Card itself holds the answer to its current owner.

Long Story Short

There is no one right way to model your game. It really is all about personal preference and how you want the system to behave. That's the beautiful thing about being a programmer. As the person who is making the code-design, you get to set the rules for how the program will operate, and what is good object-interaction, and what is bad object-interaction.

在梵高的星空下 2024-11-25 22:02:15

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

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

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

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

Do you need to know from which Player the card came from?

Also, I would assume an object called CardGame would be the holder of all of the Player objects and would be making all of the calls to the players such that one Player object does not alter the state of another Player object, but that the CardGame object does all of that.

So, within the CardGame object you would make calls like:

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

巡山小妖精 2024-11-25 22:02:15

我想说,玩家有一只手,而 Hand.addCard(card) 和 Hand.removeCard(card) 将是此处实现的方法。

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

I would say that a Player has a Hand and that Hand.addCard( card ) and Hand.removeCard( card ) would be the methods to implement here.

The game would handle removing a card from one player and giving it to the next (via those Hand calls) because the game knows about all the player objects but the player objects shouldn't necessarily need to know about each other.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文