CS 489.02 - Computers and Games - Spring 2009
XNA Pick up the Peanuts (3D)


Loyola College > Department of Computer Science > Dr. James Glenn > CS 489.02 > Examples and Lecture Notes > XNA Pick up the Peanuts

GameModel.cs

using System;
using System.Collections.Generic;

/**
 * The current state of a game.
 *
 * @author Jim Glenn
 * @version 0.2 2/10/2009 C# version (still needs XML comments)
 * @version 0.1 1/19/2009 from LiberationGame of 1/8/2009
 */

namespace Peanuts
{
    public class GameModel
    {
        /**
         * The list of sprites in this game.
         */

        private IList<Sprite> sprites;

        /**
         * The list of sprites to be removed at the next update.  This is
         * necessary because as we iterate through sprites they may want to
         * remove other sprites, but we can't modify the list of sprites
         * as we iterate through it.
         */

        private IDictionary<Sprite, Sprite> toRemove;

        /**
         * The list of sprites to be added.
         */

        private IList<Sprite> toAdd;

        /**
         * The time of the last update to this game.
         */

        private long lastUpdate;

        /**
         * The number of milliseconds in a second.
         */

        private const double TicksPerSecond = 10000000.0;

        /**
         * Creates a new game.
         */

        public GameModel()
        {
            sprites = new List<Sprite>();
            toRemove = new Dictionary<Sprite, Sprite>();
            toAdd = new List<Sprite>();

            lastUpdate = -1;
        }

        /**
         * Returns a list of the active sprites in this game.
         *
         * @return a list of sprites in this game
         */

        public virtual IList<Sprite> GetSprites()
        {
            IList<Sprite> l = new List<Sprite>(sprites);

            foreach (Sprite s in sprites)
            {
                if (!toRemove.ContainsKey(s))
                {
                    l.Add(s);
                }
            }

            return l;
        }

        /**
         * Adds the given sprite to this game.
         *
         * @param s the sprite to add
         */

        public virtual void AddSprite(Sprite s)
        {
            toAdd.Add(s);
        }

        /**
         * Removes the given sprite from this game.
         *
         * @param s the sprite to remove
         */

        public virtual void RemoveSprite(Sprite s)
        {
            toRemove.Add(s, s);
        }

        /**
         * Returns the width of the playable area of this game.
         *
         * @return the width of this game
         */

        public virtual double GetWidth()
        {
            return 1.0;
        }

        /**
         * Returns the height of the playable area of this game.
         *
         * @return the height of this game
         */

        public virtual double GetHeight()
        {
            return 1.0;
        }

        /**
         * Removes sprites that are currently on the to-be-removed list.
         */

        private void ProcessRemovedSprites()
        {
            foreach (Sprite s in toRemove.Keys)
            {
                sprites.Remove(s);
            }

            toRemove.Clear();
        }

        /**
         * Removes sprites that are currently on the to-be-added list.
         */

        private void ProcessAddedSprites()
        {
            foreach (Sprite s in toAdd)
            {
                sprites.Add(s);
            }

            toAdd.Clear();
        }

        /**
         * Updates all the sprites in this world.
         */

        public virtual void Update()
        {
            // update sprites

            long currTime = DateTime.Now.Ticks;

            if (lastUpdate != -1)
            {
                foreach (Sprite s in sprites)
                {
                    s.Update((currTime - lastUpdate) / TicksPerSecond,
                         this);
                }
            }

            lastUpdate = currTime;

            // check for and handle collisions

            CheckCollisions();

            // process removed and added sprites

            ProcessRemovedSprites();
            ProcessAddedSprites();

            // notify view that is presumably waiting for updates from this model

            // SetChanged();
            // NotifyObservers();
        }

        /**
         * Checks for and handles collisions between sprites.  Collision
         * handling between a pair of sprites is delegated to the
         * <CODE>handleCollisions</CODE> method; subclasses should
         * override that method for actions appropriate to the particular
         * game.
         */

        protected virtual void CheckCollisions()
        {
            // iterate over the n(n-1)/2 distinct pairs of sprites

            for (int i = 0; i < sprites.Count; i++)
            {
                Sprite s1 = sprites[i];

                if (!toRemove.ContainsKey(s1))
                {
                    for (int j = i + 1; j < sprites.Count; j++)
                    {
                        Sprite s2 = sprites[j];

                        if (!toRemove.ContainsKey(s2)
                            && Close(s1, s2))
                            //&& s1.CollidesWith(s2))
                        {
                            HandleCollision(s1, s2);
                        }
                    }
                }
            }
        }

        /**
         * Checks if the two given sprites are close enough to collide.
         * This check is performed using the bounding radius: if the
         * distance between the control points of the sprites is less than
         * the sum of their radii then the sprites are close enough to
         * collide.  Because this is a very rough collision check, false
         * positives are possible: the return value may be <CODE>false</CODE>
         * even though the sprites are not in collision.  Therefore
         * a more precise collision test is necessary.
         * 
         *
         * @param s1 a sprite
         * @param s2 a sprite
         * @return false if the sprites are not in collision
         */

        private bool Close(Sprite s1, Sprite s2)
        {
            double sumRadii = s1.GetBoundingRadius() + s2.GetBoundingRadius();

            return (Math.Sqrt(Math.Pow(s1.GetX() - s2.GetX(), 2)
                      + Math.Pow(s1.GetY() - s2.GetY(), 2)) < sumRadii);
        }

        /**
         * Handles a collision between the given sprites.  This implementation
         * does nothing; subclasses should override this method to implement
         * the rules of a game.
         *
         * @param s1 a sprite
         * @param s2 a sprite that has collided with <CODE>s2</CODE>
         */

        protected virtual void HandleCollision(Sprite s1, Sprite s2)
        {
            Console.WriteLine("Oof: " + s1 + " " + s2);
        }
    }
}

Hole.cs

using Microsoft.Xna.Framework.Graphics;

/**
 * A hole dug by the elephant in Pick up the Peanuts.
 *
 * @author Jim Glenn
 * @version 0.1 1/21/2009
 */
namespace Peanuts
{
    public class Hole : Sprite
    {
        /**
         * The default size of a hole.
         */

        public const double DefaultSize = 0.01;

        /**
         * Creates a new hole at the given position.  The hole will be black.
         *
         * @param initX an x-coordinate within the world this hole will be in
         * @param initY a y-coordinate within the world this hole will be in
         */


        public Hole(double initX, double initY)
            : base(initX, initY, DefaultSize, Color.Black)
        {
        }
    }
}

Peanut.cs

using Microsoft.Xna.Framework.Graphics;

/**
 * A peanut in Pick up the Peanuts.
 *
 * @author Jim Glenn
 * @version 0.1 1/20/2009
 */

namespace Peanuts
{
    public class Peanut : Sprite
    {
        /**
         * The size of a peanut.
         */

        public const double DefaultSize = 0.01;

        /**
         * Creates a new peanut at the given position.
         * The peanut will be red.
         *
         * @param initX an x-coordinate within the world this peanut will be in
         * @param initY a y-coordinate within the world this peanut will be in
         */

        public Peanut(double initX, double initY)
            : base(initX, initY, DefaultSize, Color.Orange)
        {
        }

        /**
         * Returns the value of this peanut when eaten by the player.
         *
         * @return the value of this peanut
         */

        public int GetValue()
        {
            if (GetStateTime() < 2.0)
            {
                return 50;
            }
            else if (GetStateTime() < 4.0)
            {
                return 20;
            }
            else
            {
                return 10;
            }
        }
    }
}

PeanutsControl.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Input;

namespace Peanuts
{

    public class PeanutsControl
    {
        public delegate void ClimbHandler();
        public event ClimbHandler Climb;

        public void keyPressed(Keys k)
        {
            if (k == Keys.Space)
            {
                Climb();
            }
        }
    }
}

PeanutsGame.cs

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

namespace Peanuts
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class PeanutsGame : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        private PeanutsModel model;
        private PeanutsView view;
        private PeanutsControl controller;
        
        private Texture2D uniformTexture;
        private SpriteFont font;

        private ICollection<Keys> keysDown;
        private ICollection<Keys> keysToCheck;

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

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // set up view and projection for camera beyond bottom (y = 1.0) edge of playfield
            // and a little above the field (should really ask the model about its size instead
            // of hard-coding this)

            Matrix viewMatrix = Matrix.CreateLookAt(new Vector3(0.5f, 3.0f, -2.0f),
                                             new Vector3(0.5f, 0.0f, 0.0f),
                                             new Vector3(0.0f, 0.0f, -1.0f));

            float aspect = (float)Window.ClientBounds.Width / (float)Window.ClientBounds.Height;

            // set up perspective projection with 45 degree field of view and enough depth
            // to get everything in our scene

            Matrix projectionMatrix = Matrix.CreatePerspectiveFieldOfView((float)Math.PI / 6, aspect, 0.1f, 10.0f);

            // set up effect with the above view and projection, and with directional light aimed from over
            // player's right shoulder

            BasicEffect effect = new BasicEffect(GraphicsDevice, null);
            effect.World = Matrix.Identity;
            effect.View = viewMatrix;
            effect.Projection = projectionMatrix;
            effect.VertexColorEnabled = true;
            effect.LightingEnabled = true;
            effect.DirectionalLight0.Enabled = true;
            Vector3 lightDir = new Vector3(1.0f, -1.0f, 1.0f);
            lightDir.Normalize();
            effect.DirectionalLight0.Direction = lightDir;
            effect.DirectionalLight0.DiffuseColor = new Vector3(1.0f, 1.0f, 1.0f);
            effect.DirectionalLight0.SpecularColor = new Vector3(1.0f, 1.0f, 1.0f);

            model = new PeanutsModel();
            view = new PeanutsView(model, effect, GraphicsDevice);
            controller = new PeanutsControl();

            // set up event handling

            controller.Climb += new PeanutsControl.ClimbHandler(model.ClimbPlayer);

            keysDown = new HashSet<Keys>();
            keysToCheck = new HashSet<Keys>();
            keysToCheck.Add(Keys.Space);

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            uniformTexture = Content.Load<Texture2D>("blank");
            font = Content.Load<SpriteFont>("Arial");
            view.SetFont(font);
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            // check keys that we always care about the state of

            KeyboardState kb = Keyboard.GetState();
            int dx = 0;
            int dy = 0;
            if (kb.IsKeyDown(Keys.Up))
            {
                   dy = -1;
            }
            if (kb.IsKeyDown(Keys.Down))
            {
                dy = 1;
            }
            if (kb.IsKeyDown(Keys.Left))
            {
                   dx = -1;
            }
            if (kb.IsKeyDown(Keys.Right))
            {
                dx = 1;
            }
            model.MovePlayer(dx, dy);

            // check keys that we only care about when they are first pressed

            foreach (Keys k in keysToCheck)
            {
                if (kb.IsKeyDown(k))
                {
                    if (!keysDown.Contains(k))
                    {
                        // key is down, wasn't down before
                        keysDown.Add(k);
                        controller.keyPressed(k);
                    }
                }
            }

            // update the keys that are down

            ICollection<Keys> upKeys = new HashSet<Keys>();
            foreach (Keys k in keysDown)
            {
                if (kb.IsKeyUp(k))
                {
                    upKeys.Add(k);
                    // controller.keyReleased(k);
                }
            }
            foreach (Keys k in upKeys)
            {
                keysDown.Remove(k);
            }

            // update the model

            model.Update();

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

            view.Draw(); 

            base.Draw(gameTime);
        }
    }
}

PeanutsModel.cs

using System;

/**
 * A game of Pick up the Peanuts.  Pick up the Peanuts has a player
 * and an elephant who race to pick up peanuts.
 *
 * @author Jim Glenn
 * @version 2K9.02 translated to C# (still needs XML comments
 * @version 2K9 translated to Java
 * @version 1.0 1982 or so in Atari BASIC
 */

namespace Peanuts
{
    public class PeanutsModel : GameModel
    {
        /**
         * The objects in this game.  Distinguished objects (OK, all of them)
         * are kept separately from the list for easy access.
         */

        private Player player;
        private Elephant elephant;
        private Peanut peanut;

        private const long ticksPerSecond = 10000000;

        /**
         * The amount of time this game has been going, in seconds.
         */

        private long startTime;

        /**
         * The length of a game, in milliseconds.
         */

        private const long gameLength = 180 * ticksPerSecond;

        /**
         * The player's score in this game.
         */

        private int score;

        private Random rand;

        /**
         * Creates a new Pick up the Peanut game with player and elephant
         * at their starting positions (left and right respectively)
         * and a randomly placed peanut.
         */

        public PeanutsModel()
        {
            // create a random number generator

            rand = new Random();

            // create player near middle of left edge
            player = new Player(0.1, 0.5);
            AddSprite(player);

            // create elephant near middle of right edge

            elephant = new Elephant(0.9, 0.5);
            AddSprite(elephant);

            // throw a peanut at a random location

            MakePeanut();

            // initialize player's score

            score = 0;

            // record start time

            startTime = DateTime.Now.Ticks;
        }

        /**
         * Returns the player's score in this game.
         *
         * @return the player's score
         */

        public int GetPlayerScore()
        {
            return score;
        }

        /**
         * Returns the current peanut in this game.
         *
         * @return the peanut
         */

        public Peanut GetPeanut()
        {
            return peanut;
        }

        /**
         * Forwards move events to the player.  The direction is given
         * as an x- and a y-component.
         *
         * @param dx -1, 0, or 1
         * @param dy -1, 0, or 1
         */

        public void MovePlayer(int dx, int dy)
        {
            player.Move(dx, dy);
        }

        /**
         * Climbs the player out of a hole.
         */

        public void ClimbPlayer()
        {
            player.Climb();
        }

        /**
         * Places a new peanut randomly in this game.
         */

        private void MakePeanut()
        {
            if (peanut != null)
            {
                RemoveSprite(peanut);
            }

            peanut = new Peanut(rand.NextDouble(), rand.NextDouble());
            AddSprite(peanut);
        }

        /**
         * Updates this game.
         */

        public override void Update()
        {
            if (DateTime.Now.Ticks - startTime < gameLength)
            {
                base.Update();
            }
        }

        /**
         * Returns the amount of time left in this game, in seconds.
         *
         * @return the time left
         */

        public int GetTimeLeft()
        {
            return (int)((gameLength - (DateTime.Now.Ticks - startTime)) / ticksPerSecond);
        }


        /**
         * Handles collisions between the given objects.
         *
         * @param s1 a sprite
         * @param s2 a sprite that has collided with s1
         */

        protected override void HandleCollision(Sprite s1, Sprite s2)
        {
            // do nothing when something collides with a score

            if (s1 is ValueDisplay || s2 is ValueDisplay)
            {
                return;
            }

            if (s1 is Peanut || s2 is Peanut)
            {
                // swap to make s1 the one that ate the peanut

                if (s1 is Peanut)
                {
                    Sprite temp = s1;
                    s1 = s2;
                    s2 = temp;
                }

                if (s1 is Player)
                {
                    // player ate the peanut

                    Peanut p = s2 as Peanut;

                    int value = p.GetValue();

                    score += value;
                    AddSprite(new ValueDisplay(p.GetX(), p.GetY(), value));
                }
                else if (s1 is Elephant)
                {
                    // elephant ate the peanut

                    (s1 as Elephant).EatPeanut();
                }
                else
                {
                    // eww!
                }

                // make a new peanut

                MakePeanut();
            }
            else if (s1 is Elephant && s2 is Player
                 || s1 is Player && s2 is Elephant)
            {
                // elephant tramples player

                player.Trample();
            }
            else if (s1 is Player && s2 is Hole)
            {
                // player steps in hole

                player.StepInHole();
                RemoveSprite(s2);
            }
            else if (s1 is Hole && s2 is Player)
            {
                // hole steps on player

                player.StepInHole();
                RemoveSprite(s1);
            }
        }
    }

}

PeanutsView.cs

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Peanuts
{
    public class PeanutsView
    {
        private PeanutsModel model;
        private BasicEffect effect;
        private GraphicsDevice device;
        private SpriteBatch spriteBatch;
        private SpriteFont font;

        public PeanutsView(PeanutsModel m, BasicEffect e, GraphicsDevice d)
        {
            model = m;
            effect = e;
            device = d;
            spriteBatch = new SpriteBatch(device);
        }

        public void SetFont(SpriteFont f)
        {
            font = f;
        }

        public void Draw()
        {
            // draw score and time

            if (font != null)
            {
                spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Deferred, SaveStateMode.SaveState);
                spriteBatch.DrawString(font, "" + model.GetPlayerScore(), new Vector2(20, 20), Color.White);
                spriteBatch.DrawString(font, "" + model.GetTimeLeft(), new Vector2(200, 20), Color.White);
                spriteBatch.End();
            }
 
            // draw sprites and play field

            IList<Sprite> sprites = model.GetSprites();

            device.VertexDeclaration = new VertexDeclaration(device, VertexPositionNormalColor.VertexElements);

            effect.Begin();
            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Begin();

                // draw all of the sprites

                foreach (Sprite s in sprites)
                {
                    if (s is ValueDisplay)
                    {
                    }
                    else
                    {
                        DrawSprite(s);
                    }
                }
             
                // draw the playfield

                Vector3[] fieldFan = new Vector3[] {
                        new Vector3(0.0f, 0.0f, 0.0f),
                        new Vector3((float)model.GetWidth(), 0.0f, 0.0f),
                        new Vector3((float)model.GetWidth(), (float)model.GetHeight(), 0.0f),
                        new Vector3(0.0f, (float)model.GetHeight(), 0.0f),
                        new Vector3(0.0f, 0.0f, 0.0f)
                };

                renderTriangleFan(fieldFan, Color.Green);

                pass.End();
            }
            effect.End();

            
        }

        /// <summary>
        /// Draws the given sprite.  The current implementation draws sprites as
        /// four-sided pyramids
        /// </summary>
        /// <param name="s">the sprite to draw</param>

        private void DrawSprite(Sprite s)
        {
            // get the center and radius

            float centerX = (float)s.GetX();
            float centerY = (float)s.GetY();
            float r = (float)s.GetBoundingRadius();

            // compute top, bottom, left, and right

            float top = centerY - r;
            float bottom = centerY + r;
            float left = centerX - r;
            float right = centerX + r;

            // define z-values

            float peakZ = -0.1f;
            float baseZ = -0.05f;

            // create fan of vertices for sides of pyramid

            Vector3[] pyramid = new Vector3[]
                { new Vector3(centerX, centerY, peakZ), // top of pyramid
                    new Vector3(left, top, baseZ),
                    new Vector3(right, top, baseZ),
                    new Vector3(right, bottom, baseZ),
                    new Vector3(left, bottom, baseZ),
                    new Vector3(left, top, baseZ)
                };

            renderTriangleFan(pyramid, s.GetColor());
        }

        /// <summary>
        /// Draws the given triangle fan on the current graphics device.
        /// This should be invoked within a technique's pass.
        /// </summary>
        /// <param name="fan">A list of points on the fan, with the fan point in element 0</param>
        /// <param name="c">The color of the triangles in the fan</param>

        void renderTriangleFan(Vector3[] fan, Color c)
        {
            // we convert the fan to a list so we can give each face its
            // proper normal

            int numTriangles = fan.Length - 2;
            VertexPositionNormalColor[] verts = new VertexPositionNormalColor[numTriangles * 3];

            // go over all triangles in the fan

            for (int t = 0; t < numTriangles; t++)
            {
                // do positions of vertices of triangle number t

                verts[t * 3].Position = fan[0];
                verts[t * 3 + 1].Position = fan[t + 1];
                verts[t * 3 + 2].Position = fan[t + 2];

                // and set colors

                for (int i = 0; i < 3; i++)
                {
                    verts[t * 3 + i].Color = c;
                }

                // compute normal and assign to vertices

                Vector3 side1 = fan[t + 1] - fan[0];
                Vector3 side2 = fan[t + 2] - fan[0];
                Vector3 normal = Vector3.Cross(side2, side1);
                normal.Normalize();

                for (int i = 0; i < 3; i++)
                {
                    verts[t * 3 + i].Normal = normal;
                }
            }

            // draw the triangle list

            device.DrawUserPrimitives<VertexPositionNormalColor>(PrimitiveType.TriangleList, verts, 0, numTriangles);
        }
    }
}

Player.cs

using Microsoft.Xna.Framework.Graphics;

/**
 * A human player in Pick up the Peanuts.
 *
 * @author Jim Glenn
 * @version 0.2 2/10/2009 C# version (still needs XML comments)
 * @version 0.1 1/21/2009
 */

namespace Peanuts
{
    public class Player : Sprite
    {
        /**
         * The size of the human player.
         */

        public const double DefaultSize = 0.025;

        /**
         * The amount of time, in seconds, that this player takes to recover
         * from being trampled.
         */

        private const double RecoveryTime = 0.5;

        /**
         * The amount of time, in seconds, that this player takes to climb
         * out of an elephant hole.
         */

        private const double ClimbingTime = 0.2;

        /**
         * Constants for state.
         */

        private const int stateTrampled = 1;
        private const int needsToClimb = 2;

        /**
         * The direction this player will go when it recovers from being trampled.
         */

        private int recoverDx;
        private int recoverDy;

        /**
         * The amount of time it will take this player to recover from
         * whatever mishap has befalled it.
         */

        private double recoveryTime;

        /**
         * Creates a new human player at the given position.
         * The player will be red.
         *
         * @param initX an x-coordinate within the world this playwe will be in
         * @param initY a y-coordinate within the world this Player will be in
         */

        public Player(double initX, double initY)
            : base(initX, initY, DefaultSize, Color.Red)
        {
        }

        /**
         * Returns the maximum speed of this player.
         *
         * @return the speed
         */

        public override double GetMaximumSpeed()
        {
            return 0.2;
        }

        /**
         * Changes the velocity of this player so that it is moving in
         * the given direction.  Its speed will be set to the maximum.
         *
         * @param dx -1, 0, or 1
         * @param dy -1, 0, or 1
         */

        public void Move(int dx, int dy)
        {
            if (GetState() == StateNormal)
            {
                SetVelocity(GetMaximumSpeed() * dx, GetMaximumSpeed() * dy);
            }
            else
            {
                recoverDx = dx;
                recoverDy = dy;
            }
        }

        /**
         * Tramples this player.  This player will not be able to move until
         * it recovers.
         */

        public void Trample()
        {
            if (GetState() != stateTrampled)
            {
                SetState(stateTrampled);
                recoverDx = 0;
                recoverDy = 0;
                recoveryTime = RecoveryTime;
            }
        }

        /**
         * Puts the player in a hole.  The player will not be able
         * to move until it climbs out.
         */

        public void StepInHole()
        {
            // treat the same as a trampling, but with a different recovery time

            if (GetState() != stateTrampled)
            {
                SetState(stateTrampled);
                recoverDx = 0;
                recoverDy = 0;
                recoveryTime = ClimbingTime;
            }
        }

        /**
         * Climbs this player out of a hole.
         */

        public void Climb()
        {
            if (GetState() == needsToClimb)
            {
                SetVelocity(recoverDx, recoverDy);
                SetState(StateNormal);
            }
        }

        /**
         * Updates this player.
         *
         * @param t the time since the last update
         * @param m the model this player belongs to
         */

        public override void Update(double t, GameModel m)
        {
            if (GetState() == stateTrampled)
            {
                if (GetStateTime() > recoveryTime)
                {
                    SetState(needsToClimb);
                }
                else
                {
                    SetVelocity(0, 0);
                }
            }

            base.Update(t, m);

            // do wraparound

            if (x < GetBoundingRadius() / 2)
            {
                x = m.GetWidth() - GetBoundingRadius() / 2;
            }
            else if (x + GetBoundingRadius() / 2 > m.GetWidth())
            {
                x = GetBoundingRadius() / 2;
            }

            if (y < GetBoundingRadius() / 2)
            {
                y = m.GetHeight() - GetBoundingRadius() / 2;
            }
            else if (y + GetBoundingRadius() / 2 > m.GetHeight())
            {
                y = GetBoundingRadius() / 2;
            }
        }
    }

}

Program.cs

using System;

namespace Peanuts
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        static void Main(string[] args)
        {
            using (PeanutsGame game = new PeanutsGame())
            {
                game.Run();
            }
        }
    }
}

Sprite.cs

using System;
using Microsoft.Xna.Framework.Graphics;

/**
 * A moving object in a game.  Sprites have positions, velocities, and
 * sizes.  The size is given as the radius of a circle that surrounds the
 * sprite.  Such a size is intended only for an approximate determination
 * if two sprites collide.
 *
 * @author Jim Glenn
 * @version 0.1 1/19/2009 from Liberation's Sprite.java of 1/8/2009
 */

namespace Peanuts
{
    public class Sprite
    {
        /**
         * The x-coordinate of this sprite.
         */

        protected double x;

        /**
         * The y-coordinate of this sprite.
         */

        protected double y;

        /**
         * The x-component of this sprite's velocity.
         */

        protected double vx;

        /**
         * The y-component of this sprite's velocity.
         */

        protected double vy;

        /**
         * The size of a circle that encloses this sprite.
         */

        protected double radius;

        /**
         * The color of this sprite.
         */

        protected Color color;

        /**
         * The state of this sprite.
         */

        protected int state;

        /**
         * The normal state of a sprite.  This constant is defined to be 0.
         */

        public const int StateNormal = 0;

        /**
         * The amount of time this sprite has spent in its current state.
         * Measured in seconds.
         */

        private double stateTime;

        /**
         * Creates a new sprite at the given position.
         * The sprite will be black and stationary.
         *
         * @param initX the x-coordinate of the new sprite
         * @param initY the y-coordinate of the new sprite
         * @param r the radius of a circle that bounds the new sprite
         */

        public Sprite(double initX, double initY, double r)
            : this(initX, initY, r, 0.0, 0.0, Color.Black)
        {
        }

        /**
         * Creates a new sprite at the given position.
         * The sprite will be stationary.
         *
         * @param initX the x-coordinate of the new sprite
         * @param initY the y-coordinate of the new sprite
         * @param r the radius of a circle that bounds the new sprite
         * @param c the color of the new sprite
         */

        public Sprite(double initX, double initY, double r, Color c)
            : this(initX, initY, r, 0.0, 0.0, c)
        {
        }

        /**
         * Creates a new sprite at the given position.
         *
         * @param initX the x-coordinate of the new sprite
         * @param initY the y-coordinate of the new sprite
         * @param r the radius of a circle that bounds the new sprite
         * @param initVx the x-component of the velocity of the new sprite
         * @param initVy the y-component of the velocity of the new sprite
         * @param c the color of the new sprite
         */

        public Sprite(double initX, double initY, double r, double initVx, double initVy, Color c)
        {
            x = initX;
            y = initY;
            radius = r;
            vx = initVx;
            vy = initVy;
            color = c;
            state = StateNormal;
            stateTime = 0.0;
        }

        /**
         * Returns the x-coordinate of this sprite.
         *
         * @return the x-coordinate of this sprite
         */

        public double GetX()
        {
            return x;
        }

        /**
         * Returns the y-coordinate of this sprite.
         *
         * @return the y-coordinate of this sprite
         */

        public double GetY()
        {
            return y;
        }

        /**
         * Returns the color of this sprite.
         *
         * @return the color of this sprite
         */

        public virtual Color GetColor()
        {
            return color;
        }

        /**
         * Returns the maximum speed of this sprite.  This implementation
         * returns positive infinity, indicating no limit.  Subclasses should
         * override this method so that <CODE>update</CODE> will take
         * a maximum speed into account.
         */

        public virtual double GetMaximumSpeed()
        {
            return Double.PositiveInfinity;
        }

        /**
         * Sets the velocity of this sprite.  If the given velocity exceeds the
         * maximum, it will be reduced so that it equals the maximum.  In such
         * a case the direction will remain the same.
         *
         * @param vx the new velocity in the horizontal direction
         * @param vy the new velocity in the vertical direction
         */

        public void SetVelocity(double vxNew, double vyNew)
        {
            vx = vxNew;
            vy = vyNew;

            // limit speed

            double v = Math.Sqrt(vx * vx + vy * vy);
            double maxV = GetMaximumSpeed();

            if (v > maxV)
            {
                vx /= v / maxV;
                vy /= v / maxV;
            }
        }

        /**
         * Returns the radius of a circle that encloses this sprite.
         *
         * @return the bounding radius of this sprite
         */

        public double GetBoundingRadius()
        {
            return radius;
        }

        /**
         * Returns the polygonal outline of this Sprite.  This implementation
         * returns the square inscribed in the bounding circle as defined
         * by <CODE>getBoundingRadius</CODE> and that has sides parallel to the
         * axes of the coordinate system.  Subclasses should override this
         * method for sprites of other shapes.
         *
         * @return the outline of this sprite
         */

        public virtual void GetOutline()
        {
        }

        /**
         * Determines if this sprite collides with the given sprite.
         * Two sprites are considered in collision if their outlines intersect.
         * (Note that this ignores the special case of one sprite completely
         * inside the other.)
         *
         * @return true if and only if the two sprites are in collision
         */

        public bool CollidesWith(Sprite s)
        {
            return false;
        }

        /**
         * Returns the list of points on the outline of this sprite.  The
         * starting point is on the list at the start and at the end.
         * This implementation currently returns an <CODE>ArrayList</CODE>
         * in order to facilitate random access to the points.
         *
         * @return a list of points on the outline of this sprite
         */

        private void GetPointsList()
        {
        }

        /**
         * Updates this sprite's position and velocity.  The velocity will be
         * adjusted so that it does not exceed the maximum speed as specified by
         * the <CODE>getMaximumSpeed</CODE> method.
         *
         * @param t the time since the last update, in seconds
         * @param w the world this sprite belongs to
         */

        public virtual void Update(double t, GameModel m)
        {
            stateTime += t;

            // update position

            x += vx * t;
            y += vy * t;
        }

        /**
         * Returns the state of this sprite.
         *
         * @return the current state
         */

        public int GetState()
        {
            return state;
        }

        /**
         * Sets the state of this sprite.  The state timer is reset to 0.
         *
         * @param s the new state
         */

        public void SetState(int s)
        {
            state = s;
            stateTime = 0.0;
        }

        /**
         * Returns the amount of time this sprite has spent in its current state.
         *
         * @return the time in the current state
         */

        public double GetStateTime()
        {
            return stateTime;
        }
    }

}

ValueDisplay.cs

using Microsoft.Xna.Framework.Graphics;

/**
 * A sprite that shows the value of an eaten peanut.
 *
 * @author Jim Glenn
 * @version 0.2 2/10/2009 translated to C# (still needs XML comments)
 * @version 0.1 1/21/2009
 */
namespace Peanuts
{
    public class ValueDisplay : Sprite
    {
        /**
         * The value displayed.
         */

        private int value;

        /**
         * The default size of a display.
         */

        public const double DefaultSize = 0.03;

        /**
         * The default vertical speed of a display.
         */

        public const double DefaultSpeed = -0.03;

        /**
         * The default amount of time a display will exist.
         */

        public const double DefaultTime = 1.5;

        /**
         * Creates a new sprite that displays the given value.
         *
         * @param v the value to display
         */

        public ValueDisplay(double initX, double initY, int v)
            : base(initX, initY, DefaultSize, 0.0, DefaultSpeed, Color.Black)
        {
            value = v;
        }

        /**
         * Returns the value in this display.
         *
         * @return the displayed value
         */

        public int GetValue()
        {
            return value;
        }

        /**
         * Returns the current color of this display.
         *
         * @return the color of this display.
         */

        public override Color GetColor()
        {
            return new Color((byte)(GetStateTime() / DefaultTime * 255),
                            (byte)(GetStateTime() / DefaultTime * 255),
                            (byte)(GetStateTime() / DefaultTime * 255));
        }

        /**
         * Updates this sprite's position and checks for its expiration.
         *
         * @param t the time since the last update, in seconds
         * @param w the world this sprite belongs to
         */

        public override void Update(double t, GameModel m)
        {
            base.Update(t, m);

            // check for expiration

            if (GetStateTime() > DefaultTime)
            {
                m.RemoveSprite(this);
            }
        }
    }
}

VertexPositionNormalColor.cs

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Peanuts
{
    /// <summary>
    /// A vertex type that specifies a position, surface normal, and color.
    /// </summary>

    public struct VertexPositionNormalColor
    {
        public Vector3 Position;
        public Vector3 Normal;
        public Color Color;

        public static int SizeInBytes = 7 * sizeof(float); // 3 for Vector3, 1 for Color
        public static VertexElement[] VertexElements = new VertexElement[]
                {
                    new VertexElement(0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0),
                    new VertexElement(0, sizeof(float) * 3, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0),
                    new VertexElement(0, sizeof(float) * 6, VertexElementFormat.Color, VertexElementMethod.Default, VertexElementUsage.Color, 0)
                };
    }
}
This code can also be downloaded from the files
GameModel.cs, Hole.cs, Peanut.cs, PeanutsControl.cs, PeanutsGame.cs, PeanutsModel.cs, PeanutsView.cs, Player.cs, Program.cs, Sprite.cs, ValueDisplay.cs, and VertexPositionNormalColor.cs.