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.