XMLpitstop.com   |  VBnetexpert.com   |  Community Credit  
 
 
Pitstop Search:  
in
 
Sign in | Join | Help
 
 
  Blog
    Home  
 
  Entries By Date
 
<September 2008>
SunMonTueWedThuFriSat
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011
 
 
  Blog Categories
   
 
  Archives
    November 2008 (2)  
    September 2008 (3)  
    August 2008 (3)  
    June 2008 (4)  
    May 2008 (2)  
    April 2008 (3)  
    March 2008 (3)  
    February 2008 (5)  
    December 2007 (4)  
    November 2007 (1)  
    October 2007 (3)  
 
  Syndication
    RSS  
    Atom  
    Comments RSS  
  .NET Flea Market  
 

A Zune Game Sample in C#

I will start out with a disclaimer: I am not a C# programmer; I am a VB programmer.  I have heard countless people say, "Why don't you just learn C# and be done with it.  They're just the same."  After this little experiment, I'm a little more convinced, but I am certainly more productive in VB, so that's where I will stay.

So what is the purpose of this post (and likely future posts)?  Because I have a need to share knowledge and help others, and there is a need in the Zune community to get people up to speed with XNA programming.  As previous posts mention, I'm doing Zune programming in VB.  I realize this is not for everyone, least of all beginners.  So I will apply some concepts that are universal to .NET programming in a C# format in order to promote some skill building.

The format I am going to use is to put out all the code first, then walk through it line-by-line in excruciating detail.  This way, you can hopefully skip over lines that you understand and just jump to a concept that is unfamiliar.  So scan through the full code listing, mentally compiling it and visualizing different parameters being passed and what the code path would be.  If you aren't sure what the effect of a line would be, check the detail for that line.

Ok.  The first sample I could think of, because of elevated interest in it, is cards.  This sample mixes a bit of basic programming with a sample of "what's the best way to...".  The sample is pretty basic, but can be improved upon rather easily because it's going to be built with planned expansion.

First some general terms and some design discussion.  The term "class" refers to a code structure.  A class should define something of interest to your program.  When you talk about your program to other people, the nouns you use in the description are excellent candidates for classes.  Consider the statement, "My game has a car driving around a course dodging potholes and barricades".  Thinking in code, you should immediately think: "I need classes for car, course, pothole, and barricade." 

The term "property" refers to a piece of information contained in the class.  This is data that makes the instance of the class unique.  These are the adjectives in your description.  The statement: "The player's car can be red or blue and can be upgraded with weapons."  You should be visualizing that the car class needs at least two properties: color and weapons.  When visualizing properties, don't consider the values of the properties - like red and blue - think of how to store the values.

Finally, "method" refers to an action that the class can perform, either on itself or on another instance of a class.  When describing games, these are the verbs.  "The car can speed up, turn left and right, and brake."  You should be thinking "accelerate, turn, brake" for methods on the car class.

Now, that you can translate descriptions into classes, you are now officially a software architect.  Welcome to the world of software design.  Here's the description of the "game" we are going to create:

This game has a deck of cards.  You will draw a card and the value will be displayed to you.  After all the cards have been drawn, the deck is reshuffled and the first card is drawn again. That's it.

When I read that, I think the objects should be: card and card deck.  The properties of card should be: value.  The methods of card deck should be: shuffle.  What about having "display" on the card class?  In this case, we are going to assign all display duties to the game class.  And XNA has a method for that: Draw().  That doesn't mean you will always do it that way.  Maybe in a future revision I'll illustrate the difference.

Hopefully you interpreted the description the same as I did, because that's how we're going to build it.  In this first example, the card class is going to be handled by String.  So all of our cards are going to be strings.  Later we'll make it a class of its own.  The class we are going to focus on is the card deck. 

When you think of a deck of cards, you can draw parallels to lots of programming concepts: arrays, lists, collections, but the most appropriate programming concept is the Stack.  A stack is an auto-sizing array where you can only put things on and take things off.  You can only get at the last thing put on the stack, just like you can only take off the last card put on a deck of cards.  It's a perfect fit.  .NET gives us a Stack class that we can inherit from and put in our extra method: shuffle.  Here's the code:

using System;
using System.Collections.Generic;

namespace CardsSample
{
    class CardStack<T> : Stack<T>
    {
        Random rndm;

        public CardStack()
        {
            rndm = new Random();
        }

        public CardStack(CardStack<T> cards)
        {
            rndm = new Random();

            while (cards.Count > 0)
            {
                this.Push(cards.Pop());
            }
        }

        public void Shuffle()
        {
            List<T> cards = new List<T>();
            T currentCard;
            int randomIndex;

            while (this.Count > 0)
            {
                cards.Add(this.Pop());
            }

            while (cards.Count > 0)
            {
                randomIndex = rndm.Next(0, cards.Count);
                currentCard = cards[randomIndex];
                cards.RemoveAt(randomIndex);

                this.Push(currentCard);
            }

        }
    }
}

 

Here's the explanation:

Line 1&2 (using ...): We have using statements to reduce the amount of code we have to type.  If we didn't have them, every time we defined a Random variable, we would have to use System.Random, and instead of typing Stack<T>, it would be System.Collections.Generic.Stack<T>. 

Line 4 (namespace...): Classes have namespaces to help organize the code library.  If we didn't have a unique namespace and we just happened to call our class "Stack", how would .NET know whether any variable of type "Stack" was ours or the one in System.Collections.Generic?  Yes, that's right.  The using statements are namespaces.

Line 6 (class...): This is the beginning of the definition of our class.  There are a couple of elements on this line that are not going to be something you use all the time.  The first is <T>.  This makes our class a Generic class that can be used for any data type.  Big-picture, we want this to be a stack of cards, but right now, we are using strings as our cards.  This class could have been written using <string> instead of <T>, but when we converted it to work with our later card class, we'd have to change it all again.  My making it generic, we can use strings, cards, or anything else.  The other element is the colon and the class following it.  This indicates that our class is inheriting from Stack<T>.  We get a lot of pre-built functionality from Stack, and the <T> designation means that however we define our class (strings, cards, whatever), the Stack class we are sitting on top of will be the same.

line 8 (Random...): This is the definition of our random number generator we'll use when shuffling.  This variable is only seen and used inside the class, and that's how we want it.  No other class should care about the deck's random number generator.

Line 10 (public...): This is our constructor for the class.  When another piece of code calls "New CardStack<string>()", this is the code that runs.  The term "constructor" means the method that is called when creating a new instance of a class.

Line 12 (rndm =): This is where we instantiate the random class.  If we didn't do that, the shuffle method would error with an "Object reference not set" error.  Just by declaring a variable doesn't always mean it's ready to use.

Line 15 (public ...): This is another constructor, but this one takes a parameter.  If you've ever created a new class and Visual Studio shows the yellow help with (1 of 15), showing all the different ways you can create a new object, that's what we're doing here.  If you write code new "CardStack<string>(", you will be prompted for either of the two ways to create it, either empty, by passing no parameters, or filled, by passing in another CardStack instance.  Why would we want to create a filled CardStack?  That will be shown in the game code.

Line 17 (rndm...): Just like Line 12

Line 19 (while...): The constructor is there to fill the stack with items from another stack.  The while loop will do this.  Notice we are looking at "cards.count".  What is "cards" and how do we know we can use it?   This is the parameter we passed in: its name is cards and it's type is CardStack<T>.  "cards" is only usable within this method.  If we edit line 15 and change "cards" to some other name, we also need to change it on this line so it is referenced properly.

Line 21 (this.push...): This looks like magic to a first-timer.  The key is to read it from the inside out.  Inside the parentheses, the statement "cards.Pop()" will return the top item off the card stack that was passed in.  You might think you need to assign it to a variable first, but you don't.  So right now, we have a card floating in limbo. The statement outside that, "this.Push()", captures that card and puts it on the top of the stack for the current class instance.  The keyword "this" always means the current instance of the class.  It makes sense that you would need that when you have 10 instances of the CardStack class going at once.

Line 25 (public void...): This is our shuffle method.  Because we used the keyword "public", other code can see it and execute it.  Because we used "void", other code knows that nothing is coming back when this method gets called.  The method definition doesn't specify any parameters.

line 27 (List<T>...): This variable is going to temporarily hold all the items in the stack.  Remember, we can only access the last item on a stack, so that's going to seriously limit how random we can be.  A list however, can have any of its items accessed at any time.  So that's what we'll use.  The <T> designation means to use the exact same type that was used when we defined the cardstack class - string, card, whatever.  We are also creating a new instance of the list on the same line.  Since we are going to use it right away, we can do this.

Line 28 (T ...): This might look a little odd because there is no data type T.  But this is actually a reference to the <T> we've been using all over the place.  Whatever we use when we create CardStack<T> gets used here.  CardStack<string> defines currentCard as string, CardStack<card> would define currentCard as card.

Line 29 (int...): This is used to hold a random number when we pull from the temporary list.  We can't do magic like on line 21, because we need this number on two different lines.

Line 31 (while...): We're going to loop through all the items in the current stack and put them in the temporary list.  We'll keep pulling items out until its empty.  There are lots of other clever ways to do this and later code will probably use them.

Line 32 (cards.add...): This is just like line 21, except instead of adding to a stack, we're adding to a list.  It's nice that a lot of classes use the same method names.

line 36 (while...): Instead of looping through the internal collection, we're going to loop through the temporary collection.  We're going to keep looping through it until its contents have been emptied back into the stack.

line 38 (randomindex...): We get a random number somewhere between 0 and the number of items in the temporary list.  Every time we loop, that count is going to drop by one, so we need to evaluate it each time.  That's why we don't get the count of temporary items before we start looping.  So it will be 0 through 10, then 0 through 9, and so on.

Line 39 (currentcard=...): This is where we get a random card (or string) from the temp list based on the random number we got.

line 40 (cards.removeat...): We want to remove the selected item from the temporary list so we don't get it again.  Don't worry about losing it, we still have a reference of it in currentCard.  But we need to do something with currentCard, because that's the last place we can find that card.

Line 42 (this.push...): And now we've secured the card in the current CardStack.  We've put the card on the top of the stack, so if we did a .Pop() right now, we would pull it back off.

So if the stack had 1,2,3,4,5 in it, a pop would pull off 5.  To shuffle, we popped off all the items and put them in a list.  The list looks like 5,4,3,2,1 because we pop from the stack backwards.  Then depending on the numbers from the random generator, the stack may have any order of values because we pulled from the list in a random order, never exceeding the length of the list.

Wow, that was exhausting.  But we're not done yet.  Now that we understand the cards are going to be strings, and the deck of cards is based on a Stack class, we can build the game.  Writing an XNA game is different from writing a Windows application.  Windows apps do nothing until something is done to them.  That's event-driven programming.  XNA games are running all the time, constantly checking for input and drawing the screen.  It takes a different mindset to design a game.  I'm certainly not going to say I'm great at game writing, because I tend to shoehorn in event-driven concepts into the process.  Regardless, let's see the game code.  A lot of this is boilerplate from the XNA template, so I'll only highlight the lines that I added:

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Storage;

namespace CardsSample
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        CardStack<string> drawCards;
        CardStack<string> discardCards;
        string currentCard;
        SpriteFont font;
        bool isButtonPressed;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            drawCards = new CardStack<string>();
            discardCards = new CardStack<string>();
        }

        protected override void Initialize()
        {
            base.Initialize();
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);

            font = Content.Load<SpriteFont>("MainFont");

            for (int i = 0; i < 10; i++)
            {
                drawCards.Push("Card " + i.ToString());
            }

            drawCards.Shuffle();
        }

        protected override void Update(GameTime gameTime)
        {           
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            if ((!isButtonPressed) && (GamePad.GetState(PlayerIndex.One).Buttons.A == ButtonState.Pressed))
            {
                if ((drawCards.Count == 0) && (discardCards.Count > 0))
                {
                    drawCards = new CardStack<string>(discardCards);
                    drawCards.Shuffle();
                }

                currentCard = drawCards.Pop();
                discardCards.Push(currentCard);
                
                isButtonPressed = true;
            }
            
            if (GamePad.GetState(PlayerIndex.One).Buttons.A != ButtonState.Pressed)
                isButtonPressed = false;

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            string msg;

            msg = "Your card is {0}." + Environment.NewLine   
                + "There are {1} cards remaining" + Environment.NewLine  
                + "and {2} cards have been drawn.";

            graphics.GraphicsDevice.Clear(Color.Black);
            spriteBatch.Begin();
           
            if (currentCard != null)
            {
                spriteBatch.DrawString(font, string.Format(msg, currentCard, drawCards.Count, discardCards.Count), 
                     new Vector2(20, 100), Color.White);
            }else
            {
                spriteBatch.DrawString(font, "Press Center to draw card.", 
                       new Vector2(20, 100), Color.White);
            }
            
            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}

Line 18 (CardStack...): We get to use our custom class.  This is going to hold the cards that we pull from.  Notice we actually specify a real type - string - in the definition.  When we convert to using a real class for our cards, we only need to change the definition here.  We don't need to touch the CardStack class at all.

Line 19 (cardStack...): Another of our classes.  This is going to hold the cards after we pull from the draw stack.  Think of two piles of cards (two stacks of cards, ahem).  That's what we have in these two variables.

line 20 (string...): This is going to be the variable for our currently-drawn card.  We pop from the draw stack and put it into current card and push it onto the discard stack.

Line 21 (spritefont...): We need a font to display our text.  To add a font, right-click the Content item in Visual Studio's Solution Explorer and Add New item, choose SpriteFont and give it the name MainFont, then edit the FontName field in the SprinteFont file to say Arial, and the Size to 12.

Line 22 (bool...): This variable is used to remember when the draw button is pressed.  Remember, XNA is always checking the input and the way our code is, we only want to draw a card when the button is initially pressed, not every time XNA checks to see the button is pressed.

line 29/30 (drawcards...): We instantiate the variables that will hold our cards.  Like the random variable in our CardStack, if we don't, we'll get "Object Reference Not Set" errors.

Line 42 (font=...): We load the font from the Content manager.  The name we pass in is the same as the filename we saved the spritefont as, "MainFont".  Notice we indicate what type of content we are loading in the <SpriteFont> designation.  When we get to having graphics in the game, we'll use different values.

Line 44 (for...): This is a loop that will put cards into the draw stack.  The parameters of the For loop basically are: use an int and start it at zero, loop while the value is less than 10, and increment it by one each time.  If you wanted number 10 through 20, you'd change the first parameter to initialize the int to 10 and change the second parameter to check for less than 20.  If you only wanted even numbers, change the last parameter to increment by 2 (i+=2).

line 46 (drawcards...): Here we push the new card onto the draw stack.  remember, we're just using text right now.  when we use a card class, we'll do a bit more here.  The card uses the value of the loop variable to uniquely identify it.

line 49 (drawcards...): We get to use our shuffle method.  During the loop we were putting on cards 0 through 10, so our draw stack would look like 1,2,3,3,4,5,6,7,8,9,10.  The first card we pulled off the deck would be 10, then 9, and anything but random.  So we shuffle it first.  Don't trust it?  Shuffle it a few more times.

Line 57 (if ((button...): We're in the update method.  This gets called very frequently and is used to check for input.  This particular line checks two things: is the draw button already pushed?  Or more accurately, has it been held down?  If it is, don't bother drawing a card.  If the button was not held down previously and the button is now pushed down, then let's draw a card.

line 59 (if ((drawcards...): We know we are going to draw a card, but what if there are no cards to draw - they are all discarded.  Then we reinitialize the draw stack and reshuffle.  Now you see why we created a constructor that accepts a CardStack parameter.  Sure, we could have looped through the discard stack, popped everything off, pushed it onto the draw stack, then shuffled it, but what if you needed to do that somewhere else, like if we had a reset button?  Then you'd have to write the same code again there.  Let the stack class handle it.

Line 61 (drawcards...): We create a new instance of the CardStack class and pass it the discard stack to build from.  I hear you veterans mumbling about wasting memory by creating another instance.  No one cares at this point in a tutorial.

line 62(drawcards...): We shuffle the rebuilt draw stack before we draw our first card.

Line 65(currentcard...): Whether we are dealing with a fresh deck or not, we always pull a card off and store it in the currentcard variable.  We need it stored because another method is going to display its value.

Line 66(discardCards...): So we don't lose the card, we put it on the discard stack.  we still have our reference to the card in currentCard.

Line 68 (isbuttonpressed...): We want to remember that the card has already been drawn for this button push, no matter how long the button is held in for.  Line 57 makes sure we don't keep drawing cards when we set this variable.

Line 71 (if (gamepad...): If we don't reset the isButtonpressed variable, we'll never get a second press.  So we check to see if the button has been lifted, and if so, reset the variable.  Then line 57 will catch the next button press.

line 72 (isbuttonpressed...): reset the button check variable to false.  The update method gets called like 60 times a second, so there should be no noticeable delay between button presses.

line 79 (string...): We are going to use a format string to display the message.  It's a little cleaner code.  This will be what we store the string in.

line 81-83 (msg=...): Here we are defining the format string.  We use {0}, {1}, and {2} are placeholders for where we want to insert text.

Line 86 (spritebatch...): We have to do Begin and end when writing to a spritebatch.

line 88 (if...): For presentation, we want to check of the current card is nothing.  This only happens the first time you start the program, because you haven't drawn any cards yet.  So this checks whether the current card is nothing so the right message is displayed.  This will still work when we create a card class.

line 90 (spritebatch...): We draw the string to the spritebatch for display.  This uses the string.format function, which replaces the placeholders in our format string with the value specified: current card, count of draw stack, and count of discard stack.  Notice we didn't write any code for counting the size of the stacks.  It came free when we inherited from Stack.  Our .DrawString method is using the spritefont we loaded back on line 42.  We specify a position to write the text using a Vector2 class, and specify a color.

line 91 (else...): If the first card isn't drawn yet...

Line 93 (spritebatch...): Draw the instruction to draw the first card.  This time we are using a regular string instead of the formatted string.

Line 96 (spritebatch...): You have to do End when done writing to a spritebatch.  If you forget you'll get a specific error message telling you that you forgot.

That's it.  You're done.  And I'm exhausted.  I'm not sure I'll be able to keep up this level of detail with future posts, and maybe no one needs it, but you can't be sure.  This post explained some very basic programming topics and tried to provide as much "why" as possible.  It also gave an example of inheritance and generics, two things not really meant for first-timers, so if you got it, great.  That was part of the "best way to do it" instruction.

The next step is to create the card class and use it instead.  That's actually pretty easy, so maybe it will include some other tips.

During proofreading, I noticed something that might really cause trouble for copy/pasters.  When you create a new C# project, your default namespace is created from the name of the project.  Notice in my code samples, the namespace is CardsSample.  If you create a project with a name that is different from CardsSample, you will get errors when building.  Why?

As I mentioned earlier, namespaces are used to organize code.  So the cardStack class can exist in many different places in your project as long as each definition is in its own namespace, so they won't conflict.  If you would copy and paste the code into your project, you now effectively have two namespaces in your single project.  There's nothing wrong with that, but that's not what you want or expect.  To fix it, change the namespace in the code you are pasting to match the name of your project.  Or, you can look at the namespace being used in the file Program.cs and copy that to your pasted code.

Comments

 

Exhausting Tutorial - Zune Boards said:

Pingback from  Exhausting Tutorial - Zune Boards

September 6, 2008 5:57 PM

About anachostic

That's me. Seek and ye shall find.
 
 
Copyright © . All Rights Reserved.
Powered by Community Server (Commercial Edition), by Telligent Systems