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 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 toRemove; /** * The list of sprites to be added. */ private IList 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(); toRemove = new Dictionary(); toAdd = new List(); lastUpdate = -1; } /** * Returns a list of the active sprites in this game. * * @return a list of sprites in this game */ public virtual IList GetSprites() { IList l = new List(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 * handleCollisions 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 false * 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 s2 */ protected virtual void HandleCollision(Sprite s1, Sprite s2) { Console.WriteLine("Oof: " + s1 + " " + s2); } } }