Entries:
Comments:
Posts:

Loading User Information from Channel 9

Something went wrong getting user information from Channel 9

Latest Achievement:

Loading User Information from MSDN

Something went wrong getting user information from MSDN

Visual Studio Achievements

Latest Achievement:

Loading Visual Studio Achievements

Something went wrong getting the Visual Studio Achievements

Building Multiplayer Texas Holdem Poker For The Zune

clip_image002Have you ever been interested in creating a game that harnesses XNA's powerful network library to create multiplayer experiences for the Zune device?

The Zune firmware version 3.1 brought us a professionally built incarnation of Texas Holdem that supports network play. Understanding how to send and receive data with the Zune can be a little daunting at first, but once you understand the pattern, it's easy.

To build out the entire game, you probably need about a week, but you can build some simpler examples in far less time.

This is an earlier project from before the release of my Zune game development book. Accordingly, some of the code samples you see in this article may be inconsistent with what you find in the download. The code in the article is the “correct” way to do things. The code in the download is still a work in progress.

The Workflow

Developing multiplayer games for the Zune is interesting because you have to deploy to each device individually. Once you have a build that works for you, it's helpful to run-deploy (Control+F5) to one device, leave it running there, and then plug in the other Zune and debug-deploy (F5) to it. This way you have one debuggable instance of the game running. Make sure to set the appropriate Zune device as the default in the XNA Game Studio Device Center (accessible from the Tools menu).

Starting A Network Session

Because the Zunes connect over an ad-hoc, peer-to-peer connection rather than through an access point, you will have to designate one Zune as the host device. The host is usually determined to be the one that creates a new game. Therefore, all Zunes that join the host's network session are simply peers. The difference between the host and the peers is that the host usually maintains the game state on top of executing the game as well, because the game data has to be centralized somewhere. Keep that in mind, because if one Zune is doing substantially more processing, it can lag behind and mess up your network session. Also, remember that all Zunes are running the exact same copy of the game, so the game must support both host and peer scenarios.

Create / Join / Lobby Model

Most peer-to-peer connected games allow a user to create or join a game. After doing so, the player is funneled into an area called the Lobby where they can specify their readiness. When all players are ready, the host can start the game. Some of this functionality is provided directly by the XNA Framework's Net and GamerServices libraries.

I normally create three separate screens based on the Network Game State Management sample from the Creators Club website. The first is the Create screen, which looks exactly like a lobby. It starts up a network session and waits for players to join and become ready. The code to create a network session looks like this (note that I have employed some abstraction to make my code a little more cohesive):

C#

public void CreateZuneSession(int maxNetworkPlayers)
{
    KillSession();

    try
    {
        Session = NetworkSession.Create(NetworkSessionType.SystemLink, 1,      
            maxNetworkPlayers); 
        Me = Session.LocalGamers[0];
    }
    catch (NetworkNotAvailableException)
    {
        throw new NetworkNotAvailableException("Zune wireless is not 
            enabled.");
    }
    catch (NetworkException ne)
    {
        throw ne;
    }

    if (Session == null)
        throw new NetworkException("The network session could not be 
            created.");
}

All Zune network sessions are of the type SystemLink, much like LAN-networked Xbox consoles. The ‘1' parameter specifies the number of local players – of course, on a Zune, there can only be one.

This line of code sets the Session property to a newly created network session. The Join screen, running on another Zune, will find this session asynchronously as an available network session and attempt to join it. The first step is to enumerate available network sessions:

C#

/// <summary>
/// Asynchronous: Begins to discover network sessions.
/// </summary>
public void BeginGetAvailableSessions()
{
    // Destroy any existing connections
    KillSession();

    NetworkSession.BeginFind(NetworkSessionType.SystemLink, 1, null, new 
        AsyncCallback(SessionsFoundCallback), null);
}

This code looks for SystemLink sessions and calls the callback method SessionsFoundCallback when the operation completes (successfully or unsuccessfully). If sessions are found, an event is fired. Other screens can subscribe to this event and transition to other screens or do other processing with the network session.

C#

/// <summary>
/// Called when network sessions are found.
/// </summary>
/// <param name="result"></param>
public void SessionsFoundCallback(IAsyncResult result)
{
    AvailableNetworkSessionCollection availableSessions = null;
    availableSessions = NetworkSession.EndFind(result);

    if (NetworkSessionsFound != null)
        NetworkSessionsFound(availableSessions);
}        

When all players are ready and the host presses the middle button, the network session's StartGame() method is called, which will cause all connected peers to receive a GameStarted event. This loads up the playing screen.

C#

if (allPlayersReady && atLeastTwoPlayers)
{
    if (ScreenManager.Network.Session != null)
        ScreenManager.Network.Session.StartGame();
}

That's basically how to connect up two Zunes in a network session. See my book for a much more detailed explanation.

Dealing With Cards

As Poker is a card game, you might want to develop a testable, standalone library that you can use not only to house your objects, but also to write out all the intense logic required for a game like poker. The task of determining what a player's best hand is out of seven cards (and whether it beats another player's hand) is more in-depth than you might expect.

An easy way to go about this is to create a Zune game library. I called mine CardLib. CardLib has objects such as these:

  • Card which has a suit, a value, some comparers and some utility methods)
  • Deck, which has a collection of cards and methods like Shuffle, ResetDeck, etc.
  • Dealer, which has a deck, a list of the five community cards, and methods like Shuffle, DealCard, Burn, DealFlop, DealTurn, etc.
  • HoldemHand, which contains all of the logic for determining what a hand is (always 7 cards)
  • BestHand, which takes a HoldemHand and determines what the best possible hand is within that hand

Shuffling Cards

Shuffling cards is surprisingly easy. I use the Knuth-Fisher-Yates shuffling algorithm, which takes every card in the deck and randomly swaps it with another.

C#

/// <summary>
/// Knuth-Fisher-Yates shuffling algorithm:
/// http://www.codinghorror.com/blog/archives/001015.html
/// </summary>
public void Shuffle()
{
    Random rand = new Random();
    for (int cardIndex = _cards.Count - 1; cardIndex > 0; cardIndex--)
    {
        int randomIndex = rand.Next(cardIndex + 1);
        SwapCardsByIndex(cardIndex, randomIndex);
    }
    _dealIndex = 0;
}

Poker Logic

An example method you'll see in this library is this snippet to check if the hand is a royal flush:

C#

public bool IsRoyalFlush(out List<Card> winningCards)
{
    List<Card> straightFlushCards;

    if (IsStraightFlush(out straightFlushCards))
    {                
        // Check to make sure the straight flush cards are: 10 J Q K A
        straightFlushCards.Sort();

        if (straightFlushCards[0].Value == 10 &&
            straightFlushCards[1].Value == Card.Jack &&
            straightFlushCards[2].Value == Card.Queen &&
            straightFlushCards[3].Value == Card.King &&
            straightFlushCards[4].Value == Card.Ace)
        {
            winningCards = straightFlushCards;
            return true;
        }

        else
        {
            winningCards = null;
            return false;
        }
    }
    else
    {
        winningCards = null;
        return false;
    }
}

There is a ton of similar and even more disgusting logic to peruse in the HoldemHand.cs file in the region entitled “Poker Logic Fun Times.” This logic covers every possible poker hand and could probably be refactored to be way more elegant.

When it comes time to evaluate the winner, each player's best hand is determined and they are evaluated against each other to determine the winner. This code returns a list of WinnerInfo objects, which returns the player info and a list of the cards they won with.

C#

public List<WinnerInfo> DetermineWinners()
{
    BestHand overallBestHand = null;
    HoldemPlayer winner = null;

    List<WinnerInfo> winners = new List<WinnerInfo>();

    foreach (HoldemPlayer player in _players)
    {
        if (player.Status != PlayerStatus.Folded)
        {
            HoldemHand tempHand = new HoldemHand(player.Pocket, _communityCards);
            BestHand bestHand = tempHand.GetBestHand();

            if (overallBestHand == null) // if there is no best hand yet
            {
                overallBestHand = bestHand;
                winner = player;
                winners.Add(new WinnerInfo(overallBestHand, winner, Me));
            }
            else
            {
                // if this hand beats the current best hand
                if (BestHand.Beats(bestHand, overallBestHand))
                {
                    winners.Clear();
                    overallBestHand = bestHand;
                    winner = player;
                    winners.Add(new WinnerInfo(overallBestHand, winner, Me));
                }
                else
                {
                    if (BestHand.IsEquivalentTo(bestHand, overallBestHand))
                    {
                        winners.Add(new WinnerInfo(bestHand, player, Me));
                    }
                }
            }
        }
    }

    _winners = winners;
    return winners;
}

Drawing Cards On The Screen

Rather than have 52 different sprites that represent each possible card, I just made my own sheet of Zune-sized playing cards. Horizontally, they are ordered by value (1-13) and vertically they are ordered by suit (both alphabetically and corresponding to the numeric value of the Suit enumeration in the project, 1-4).

clip_image004

When a specific card is requested to be drawn, a formula is used to calculate the source rectangle from this larger image. This is very similar to how fonts worked before spritefonts were introduced into XNA. This allows us to strip out the graphic of a specific card and draw it onscreen at some other location.

C#

private void DrawCard(Card card, Vector2 position)
{
    if (card != Card.Undefined && card.Suit != Suit.Unassigned)
        ScreenManager.SpriteBatch.Draw(_texDeck, position, GetCardSourceRect(card), Color.White);
}

private Rectangle GetCardSourceRect(Card card)
{
    if (card.Suit == Suit.Unassigned)
        throw new ArgumentException("Unassigned cards cannot be drawn.");

    int cardColumn = card.Value - 1;
    int cardRow = (int)card.Suit - 1;

    int x = cardColumn * MainScreenElements.CARD_WIDTH;
    int y = cardRow * MainScreenElements.CARD_HEIGHT;

    return new Rectangle(x, y, MainScreenElements.CARD_WIDTH, MainScreenElements.CARD_HEIGHT);
}

Managing Network Data

One thing that increases the complexity of this game (in no small way) is the management of network data. First of all, the host's actions are a superset of the peer's actions (a host is also a peer, but it also has to manage the game and network state). For example, when a peer decides to bet, it has to send a message to the host saying “I would like to bet,” and then the host will process that message and relay it to the other peers. The host is also responsible for managing whose turn it is and determining who the winner is. Theoretically, you could do a lot of this with every peer acting equally but it feels safer to me to have the host responsible for important activities like dealing cards. In fact, the host has to be the only one who can deal cards, because if each client maintained its own deck, it would be randomized for every peer when the deck is shuffled.

How Data Is Sent And Received

I will usually create a static class called NetworkMessageSender that is responsible for sending various messages. I keep an enumeration of type byte that holds the possible network messages.

I always send the byte indicating the message type first, so that when the peer receives a byte, it knows how to respond. For example, if the peer receives a CardDealt message, it can pop off a string and a card from the incoming packets. If the card is not intended for the peer, it can simply discard the message.

Data you want to send is written to a packet writer object. When the data is ready to be sent, you call the SendData method of the LocalGamer object. Depending on how important packet receipt is, you can specify the type of transmission. I use ReliableInOrder during poker because network data is exchanged relatively infrequently. Although there is only one local network gamer, you can use a foreach loop to ensure this code will work on other platforms.

C#

private static void SendData()
{
    foreach (LocalNetworkGamer gamer in NetworkSessionManager.Session.LocalGamers)
        gamer.SendData(NetworkSessionManager.PacketWriter, SendDataOptions.ReliableInOrder);
}

Specific chunks of data are then sent using various static methods. Each piece of information is written sequentially. The card is serialized into a string format before being sent. The following chunk of code is used to send a card to a player.

C#

public static void DealCards(HoldemPlayer player)
{
    NetworkSessionManager.PacketWriter.Write((byte)NetworkMessageType.Deal);
    NetworkSessionManager.PacketWriter.Write(player.Name);
    NetworkSessionManager.PacketWriter.Write(player.Pocket.Card1.Serialize());
    NetworkSessionManager.PacketWriter.Write(player.Pocket.Card2.Serialize());
    SendData();
}

On the other side of that, whenever the screen updates, the game is constantly checking for new network data. The following code is part of a method that is called from the game screen's Update loop.

C#

private void UpdateNetworkSession()
{
    NetworkSessionManager.Update();

    foreach (LocalNetworkGamer localGamer in 
        NetworkSessionManager.Session.LocalGamers)
    {
        while (localGamer.IsDataAvailable)
        {
            NetworkGamer sender;
            localGamer.ReceiveData(NetworkSessionManager.PacketReader, 
                out sender);

            // Interpret the first piece of information, the message.
            NetworkMessageType message = (NetworkMessageType)NetworkSessionManager.PacketReader.ReadByte();

            // Determine what to read and what to do based on this message type
            switch (message)
            {
                // major snippage
                case NetworkMessageType.Deal: // Happens when a card is dealt
                {
                    string playerName = NetworkSessionManager.PacketReader.ReadString();
                    string card1 = NetworkSessionManager.PacketReader.ReadString();
                    string card2 = NetworkSessionManager.PacketReader.ReadString();

                 _gameplayManager.Players[playerName].Pocket.Set(PlayerPocket.FIRST_CARD_INDEX, new 
                    Card(card1));
                 _gameplayManager.Players[playerName].Pocket.Set(PlayerPocket.SECOND_CARD_INDEX, new 
                    Card(card2));
                }
                break;
            }
        }
    }
}

There is a whole bunch of similar code that underlies how the game executes. When a message needs to be sent, the NetworkMessageSender class is used. To determine what happens when a message is received, we look at that ginormous switch statement that gets called in the screen update loop.

Remember that the host is also a peer and it will receive the very same messages it sends (unless you specify otherwise). Be careful not to double-process messages if they are sent by the host. In some cases you will need to check that the current device is or is not the host. In the sample download, this is achieved just by checking a Boolean value that is set early on in the game.

Conclusion

Building Poker for the Zune, from the ground up, is no small feat! Networked Zune games are far simpler when you have a much more limited set of possible data and messages that could be sent. For example, networked Pong, Battleship or Tetris would be pretty easy compared to poker. Turn-based games on the Zune also provide an interesting challenge in terms of what happens when a Zune drops from the network session.

This is just a small dip into networked Zune game development. For a deep dive, check out my book, Zune Game Development using XNA 3.0, also available on Apress.com as an eBook. The final chapter is a sprawling 120 pages covering how to build Crazy Eights for the Zune from the ground up.

About The Author

Dan Waters is an Academic Developer Evangelist at Microsoft, based in Tampa, FL. When he's not out showing the latest and greatest MS technology to students and faculty, he's spending time with his wife and young daughter or rocking out on one of his (far too numerous) guitars. Follow Dan on Twitter or check out his blog.

Tags:

Follow the Discussion

  • Cheat Texas HoldemCheat Texas Holdem

    Man you the best!!!

  • felicityfelicity

    Playing poker is so much fun! I love having the opportunity top lay free poker online… it is amazing! From the comfort of my own home… does it sound great? You bet! Everyone should join me for a game! Contact me! Felicity -> <a href= "http://www.bonuspoker.com/">bonus poker</a>.

Remove this comment

Remove this thread

close

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.