Browse Source

Add new XNAPacMan sample

Kenneth Pouncey 13 years ago
parent
commit
241544191b
99 changed files with 4310 additions and 0 deletions
  1. 151 0
      Samples/MacOS/XNAPacMan/Constants.cs
  2. BIN
      Samples/MacOS/XNAPacMan/Content/Audio/Sound Bank.xsb
  3. BIN
      Samples/MacOS/XNAPacMan/Content/Audio/Wave Bank.xwb
  4. 1422 0
      Samples/MacOS/XNAPacMan/Content/Audio/XNAPacManAudio.xap
  5. 322 0
      Samples/MacOS/XNAPacMan/Content/Audio/XNAPacManAudio.xml
  6. BIN
      Samples/MacOS/XNAPacMan/Content/Audio/YEPAudio.xgs
  7. BIN
      Samples/MacOS/XNAPacMan/Content/Audio/bg1.wav
  8. BIN
      Samples/MacOS/XNAPacMan/Content/Audio/bg2.wav
  9. BIN
      Samples/MacOS/XNAPacMan/Content/Audio/bg3.wav
  10. BIN
      Samples/MacOS/XNAPacMan/Content/Audio/bgghost.wav
  11. BIN
      Samples/MacOS/XNAPacMan/Content/Audio/bgghosteyes.wav
  12. BIN
      Samples/MacOS/XNAPacMan/Content/Audio/eatfruit.wav
  13. BIN
      Samples/MacOS/XNAPacMan/Content/Audio/eatghost.wav
  14. BIN
      Samples/MacOS/XNAPacMan/Content/Audio/eatpill1.wav
  15. BIN
      Samples/MacOS/XNAPacMan/Content/Audio/eatpill2.wav
  16. BIN
      Samples/MacOS/XNAPacMan/Content/Audio/extralife.wav
  17. BIN
      Samples/MacOS/XNAPacMan/Content/Audio/killed.wav
  18. BIN
      Samples/MacOS/XNAPacMan/Content/Audio/newgame.wav
  19. BIN
      Samples/MacOS/XNAPacMan/Content/Audio/newlevel.wav
  20. BIN
      Samples/MacOS/XNAPacMan/Content/Grid.txt
  21. BIN
      Samples/MacOS/XNAPacMan/Content/MenuItem.xnb
  22. BIN
      Samples/MacOS/XNAPacMan/Content/Score.xnb
  23. BIN
      Samples/MacOS/XNAPacMan/Content/ScoreEvent.xnb
  24. BIN
      Samples/MacOS/XNAPacMan/Content/bonus/Apple.png
  25. BIN
      Samples/MacOS/XNAPacMan/Content/bonus/Apple.xnb
  26. BIN
      Samples/MacOS/XNAPacMan/Content/bonus/Banana.png
  27. BIN
      Samples/MacOS/XNAPacMan/Content/bonus/Banana.xnb
  28. BIN
      Samples/MacOS/XNAPacMan/Content/bonus/Bell.png
  29. BIN
      Samples/MacOS/XNAPacMan/Content/bonus/Bell.xnb
  30. BIN
      Samples/MacOS/XNAPacMan/Content/bonus/Cherry.png
  31. BIN
      Samples/MacOS/XNAPacMan/Content/bonus/Cherry.xnb
  32. BIN
      Samples/MacOS/XNAPacMan/Content/bonus/Key.png
  33. BIN
      Samples/MacOS/XNAPacMan/Content/bonus/Key.xnb
  34. BIN
      Samples/MacOS/XNAPacMan/Content/bonus/Orange.png
  35. BIN
      Samples/MacOS/XNAPacMan/Content/bonus/Orange.xnb
  36. BIN
      Samples/MacOS/XNAPacMan/Content/bonus/Pear.png
  37. BIN
      Samples/MacOS/XNAPacMan/Content/bonus/Pear.xnb
  38. BIN
      Samples/MacOS/XNAPacMan/Content/bonus/Pretzel.png
  39. BIN
      Samples/MacOS/XNAPacMan/Content/bonus/Pretzel.xnb
  40. BIN
      Samples/MacOS/XNAPacMan/Content/bonus/Strawberry.png
  41. BIN
      Samples/MacOS/XNAPacMan/Content/bonus/Strawberry.xnb
  42. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/Board.png
  43. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/Board.xnb
  44. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/BoardFlash.png
  45. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/BoardFlash.xnb
  46. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/Crump.png
  47. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/Crump.xnb
  48. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/DyingSheetNew.png
  49. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/DyingSheetNew.xnb
  50. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/ExtraLife.png
  51. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/ExtraLife.xnb
  52. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/GhostBase.png
  53. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/GhostBase.xnb
  54. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/GhostBase2.png
  55. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/GhostBase2.xnb
  56. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/GhostChased.png
  57. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/GhostChased.xnb
  58. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/GhostEyes.png
  59. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/GhostEyes.xnb
  60. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/GhostEyesCenter.png
  61. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/GhostEyesCenter.xnb
  62. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/PacManEating1.png
  63. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/PacManEating1.xnb
  64. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/PacManEating2.png
  65. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/PacManEating2.xnb
  66. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/PacManEating3.png
  67. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/PacManEating3.xnb
  68. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/PacManEating4.png
  69. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/PacManEating4.xnb
  70. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/PacManEating5.png
  71. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/PacManEating5.xnb
  72. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/PacManEating6.png
  73. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/PacManEating6.xnb
  74. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/PacManEating7.png
  75. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/PacManEating7.xnb
  76. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/PacManEating8.png
  77. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/PacManEating8.xnb
  78. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/PacManEating9.png
  79. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/PacManEating9.xnb
  80. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/PowerPill.png
  81. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/PowerPill.xnb
  82. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/Selection.png
  83. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/Selection.xnb
  84. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/Title.png
  85. BIN
      Samples/MacOS/XNAPacMan/Content/sprites/Title.xnb
  86. BIN
      Samples/MacOS/XNAPacMan/Game.ico
  87. 442 0
      Samples/MacOS/XNAPacMan/GameLoop.cs
  88. BIN
      Samples/MacOS/XNAPacMan/GameThumbnail.png
  89. 747 0
      Samples/MacOS/XNAPacMan/Ghost.cs
  90. 86 0
      Samples/MacOS/XNAPacMan/GhostSoundsManager.cs
  91. 184 0
      Samples/MacOS/XNAPacMan/Grid.cs
  92. 90 0
      Samples/MacOS/XNAPacMan/HighScores.cs
  93. 16 0
      Samples/MacOS/XNAPacMan/Info.plist
  94. 181 0
      Samples/MacOS/XNAPacMan/Menu.cs
  95. 371 0
      Samples/MacOS/XNAPacMan/Player.cs
  96. 38 0
      Samples/MacOS/XNAPacMan/Program.cs
  97. 86 0
      Samples/MacOS/XNAPacMan/XNAPacMan.cs
  98. 154 0
      Samples/MacOS/XNAPacMan/XNAPacMan.csproj
  99. 20 0
      Samples/MonoGame.Samples.MacOS.sln

+ 151 - 0
Samples/MacOS/XNAPacMan/Constants.cs

@@ -0,0 +1,151 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace XNAPacMan {
+    /// <summary>
+    /// This class provides global access to important game constants; what fruits appear on what levels,
+    /// relative speeds of everything, timers, etc. Centralizing this data here makes it easy to change
+    /// the game settings. It also relieves the data-heavy ghost and gameloop classes from a LOT of definitions.
+    /// </summary>
+    static class Constants {
+
+        // Dispersion tiles for each ghost
+        public static readonly List<Point> scatterTilesBlinky =  new List<Point> {   new Point(21, 1),   new Point(26, 1),
+                                                                            new Point(26, 5),   new Point(21, 5)    
+        };
+        public static readonly List<Point> scatterTilesPinky = new List<Point> {   new Point(1, 1),    new Point(6, 1),
+                                                                            new Point(6, 5),    new Point(1, 5)     
+        };
+        public static readonly List<Point> scatterTilesClyde = new List<Point> {   new Point(6, 23),   new Point(9, 23),
+                                                                            new Point(9, 26),   new Point(12, 26),  
+                                                                            new Point(12, 29),  new Point(1, 29),
+                                                                            new Point(1, 26),   new Point(6, 26)    
+        };
+        public static readonly List<Point> scatterTilesInky = new List<Point> {   new Point(18, 23),  new Point(21, 23),
+                                                                            new Point(21, 26),  new Point(26, 26),
+                                                                            new Point(26, 29),  new Point(15, 29),
+                                                                            new Point(15, 26),  new Point(18, 26)
+        };
+
+        public static List<Point> scatterTiles(Ghosts identity) {
+            switch (identity) {
+                case Ghosts.Blinky:
+                    return scatterTilesBlinky;
+                case Ghosts.Clyde:
+                    return scatterTilesClyde;
+                case Ghosts.Inky:
+                    return scatterTilesInky;
+                case Ghosts.Pinky:
+                    return scatterTilesPinky;
+                default:
+                    throw new ArgumentException();
+            }
+        }
+
+        public static readonly Position startPositionBlinky = new Position { Tile = new Point(13, 11), DeltaPixel = new Point(8, 0) };
+        public static readonly Position startPositionPinky = new Position { Tile = new Point(13, 14), DeltaPixel = new Point(8, 8) };
+        public static readonly Position startPositionInky = new Position { Tile = new Point(11, 13), DeltaPixel = new Point(8, 8) };
+        public static readonly Position startPositionClyde = new Position { Tile = new Point(15, 13), DeltaPixel = new Point(8, 8) };
+        public static Position startPosition(Ghosts identity) {
+            switch (identity) {
+                case Ghosts.Blinky:
+                    return startPositionBlinky;
+                case Ghosts.Pinky:
+                    return startPositionPinky;
+                case Ghosts.Clyde:
+                    return startPositionClyde;
+                case Ghosts.Inky:
+                    return startPositionInky;
+                default:
+                    throw new ArgumentException();
+
+            }
+        }
+
+        public static int Level = 0;
+        public static int InitialJumps(Ghosts ghost, bool newLevel) {
+            if (newLevel) {
+                switch (ghost) {
+                    case Ghosts.Inky:
+                        return (int)MathHelper.Clamp((20 - Level) / 2, 0, 10);
+                    case Ghosts.Clyde:
+                        return InitialJumps(Ghosts.Inky, true) + 2;
+                    default:
+                        return 0;
+                }
+            }
+            else {
+                switch (ghost) {
+                    case Ghosts.Inky:
+                        return 1;
+                    case Ghosts.Clyde:
+                        return 2;
+                    default:
+                        return 0;
+                }
+            }
+        }
+
+        private static int[] cruiseElroyTimers_ = { 20, 30, 40, 40, 40, 50, 50, 50, 60, 60 };
+        public static int CruiseElroyTimer() {
+            if (Level >= 10) {
+                return cruiseElroyTimers_[9];
+            }
+            else {
+                return cruiseElroyTimers_[Level - 1];
+            }
+        }
+
+        public static Color colors(Ghosts identity) {
+            switch (identity) {
+                case Ghosts.Blinky:
+                    return Color.Red;
+                case Ghosts.Clyde:
+                    return Color.Orange;
+                case Ghosts.Inky:
+                    return Color.LightSkyBlue;
+                case Ghosts.Pinky:
+                    return Color.LightPink;
+                default:
+                    throw new ArgumentException();
+            }
+        }
+
+        private static int[] blueTimes_ = { 6, 6, 4, 3, 2, 6, 2, 2, 1, 5, 2, 1, 1, 3, 1, 1, 0, 1, 0, 0, 0 };
+        public static int BlueTime() {
+            return Level > blueTimes_.Length - 2 ? 0 : blueTimes_[Level - 1];
+        }
+
+        private static int[] bonusScores_ = { 100, 300, 500, 700, 700, 1000, 1000, 2000, 2000, 3000, 3000, 5000, 5000, 5000 };
+        public static int BonusScores() {
+            return Level > bonusScores_.Length - 2 ? 5000 : bonusScores_[Level - 1];
+        }
+
+        private static string[] bonusSprites_ = { "Cherry", "Strawberry", "Apple", "Bell", "Orange", "Pear", "Pretzel", "Bell", "Banana", "Key", "Key" };
+        public static string BonusSprite() {
+            return Level > bonusSprites_.Length - 2 ? "Key" : bonusSprites_[Level - 1];
+        }
+
+        private static int[] pacManSpeed_ = { 7, 9, 8, 8, 9 };
+        public static int PacManSpeed() {
+            if (5 <= Level && Level <= 20) {
+                return pacManSpeed_[4];
+            }
+            else if (5 > Level) {
+                return pacManSpeed_[Level - 1];
+            }
+            else {
+                return 10;
+            }
+        }
+
+        private static int[] ghostSpeed_ = { 13, 11, 12, 12 };
+        public static int GhostSpeed() {
+            return Level > 4 ? 11 : ghostSpeed_[Level - 1];
+        }
+    }
+}

BIN
Samples/MacOS/XNAPacMan/Content/Audio/Sound Bank.xsb


BIN
Samples/MacOS/XNAPacMan/Content/Audio/Wave Bank.xwb


+ 1422 - 0
Samples/MacOS/XNAPacMan/Content/Audio/XNAPacManAudio.xap

@@ -0,0 +1,1422 @@
+Signature = XACT3;
+Version = 18;
+Content Version = 46;
+Release = February 2010;
+
+Options
+{
+    Verbose Report = 0;
+    Generate C/C++ Headers = 1;
+}
+
+Global Settings
+{
+    Xbox File = Xbox\YEPAudio.xgs;
+    Windows File = Win\YEPAudio.xgs;
+    Header File = YEPAudio.h;
+    Exclude Category Names = 0;
+    Exclude Variable Names = 0;
+    Last Modified Low = 0;
+    Last Modified High = 0;
+
+    Category
+    {
+        Name = Global;
+        Public = 1;
+        Background Music = 0;
+        Volume = 0;
+
+        Category Entry
+        {
+        }
+
+        Instance Limit
+        {
+            Max Instances = 255;
+            Behavior = 0;
+
+            Crossfade
+            {
+                Fade In = 0;
+                Fade Out = 0;
+                Crossfade Type = 0;
+            }
+        }
+    }
+
+    Category
+    {
+        Name = Default;
+        Public = 1;
+        Background Music = 0;
+        Volume = 0;
+
+        Category Entry
+        {
+            Name = Global;
+        }
+
+        Instance Limit
+        {
+            Max Instances = 255;
+            Behavior = 0;
+
+            Crossfade
+            {
+                Fade In = 0;
+                Fade Out = 0;
+                Crossfade Type = 0;
+            }
+        }
+    }
+
+    Category
+    {
+        Name = Music;
+        Public = 1;
+        Background Music = 1;
+        Volume = 0;
+
+        Category Entry
+        {
+            Name = Global;
+        }
+
+        Instance Limit
+        {
+            Max Instances = 255;
+            Behavior = 0;
+
+            Crossfade
+            {
+                Fade In = 0;
+                Fade Out = 0;
+                Crossfade Type = 0;
+            }
+        }
+    }
+
+    Variable
+    {
+        Name = OrientationAngle;
+        Public = 1;
+        Global = 0;
+        Internal = 0;
+        External = 0;
+        Monitored = 1;
+        Reserved = 1;
+        Read Only = 0;
+        Time = 0;
+        Value = 0.000000;
+        Initial Value = 0.000000;
+        Min = -180.000000;
+        Max = 180.000000;
+    }
+
+    Variable
+    {
+        Name = DopplerPitchScalar;
+        Public = 1;
+        Global = 0;
+        Internal = 0;
+        External = 0;
+        Monitored = 1;
+        Reserved = 1;
+        Read Only = 0;
+        Time = 0;
+        Value = 1.000000;
+        Initial Value = 1.000000;
+        Min = 0.000000;
+        Max = 4.000000;
+    }
+
+    Variable
+    {
+        Name = SpeedOfSound;
+        Public = 1;
+        Global = 1;
+        Internal = 0;
+        External = 0;
+        Monitored = 1;
+        Reserved = 1;
+        Read Only = 0;
+        Time = 0;
+        Value = 343.500000;
+        Initial Value = 343.500000;
+        Min = 0.000000;
+        Max = 1000000.000000;
+    }
+
+    Variable
+    {
+        Name = ReleaseTime;
+        Public = 1;
+        Global = 0;
+        Internal = 1;
+        External = 1;
+        Monitored = 1;
+        Reserved = 1;
+        Read Only = 1;
+        Time = 1;
+        Value = 0.000000;
+        Initial Value = 0.000000;
+        Min = 0.000000;
+        Max = 15000.000000;
+    }
+
+    Variable
+    {
+        Name = AttackTime;
+        Public = 1;
+        Global = 0;
+        Internal = 1;
+        External = 1;
+        Monitored = 1;
+        Reserved = 1;
+        Read Only = 1;
+        Time = 1;
+        Value = 0.000000;
+        Initial Value = 0.000000;
+        Min = 0.000000;
+        Max = 15000.000000;
+    }
+
+    Variable
+    {
+        Name = NumCueInstances;
+        Public = 1;
+        Global = 0;
+        Internal = 1;
+        External = 1;
+        Monitored = 1;
+        Reserved = 1;
+        Read Only = 1;
+        Time = 0;
+        Value = 0.000000;
+        Initial Value = 0.000000;
+        Min = 0.000000;
+        Max = 1024.000000;
+    }
+
+    Variable
+    {
+        Name = Distance;
+        Public = 1;
+        Global = 0;
+        Internal = 0;
+        External = 0;
+        Monitored = 1;
+        Reserved = 1;
+        Read Only = 0;
+        Time = 0;
+        Value = 0.000000;
+        Initial Value = 0.000000;
+        Min = 0.000000;
+        Max = 1000000.000000;
+    }
+}
+
+Wave Bank
+{
+    Name = Wave Bank;
+    Xbox File = Xbox\Wave Bank.xwb;
+    Windows File = Win\Wave Bank.xwb;
+    Xbox Bank Path Edited = 0;
+    Windows Bank Path Edited = 0;
+    Seek Tables = 1;
+    Compression Preset Name = <none>;
+    Xbox Bank Last Modified Low = 0;
+    Xbox Bank Last Modified High = 0;
+    PC Bank Last Modified Low = 0;
+    PC Bank Last Modified High = 0;
+    Header Last Modified Low = 0;
+    Header Last Modified High = 0;
+    Bank Last Revised Low = 711905748;
+    Bank Last Revised High = 29970206;
+
+    Wave
+    {
+        Name = bg1;
+        File = bg1.wav;
+        Build Settings Last Modified Low = 673905748;
+        Build Settings Last Modified High = 29970206;
+
+        Cache
+        {
+            Format Tag = 0;
+            Channels = 1;
+            Sampling Rate = 44100;
+            Bits Per Sample = 1;
+            Play Region Offset = 44;
+            Play Region Length = 35162;
+            Loop Region Offset = 0;
+            Loop Region Length = 0;
+            File Type = 1;
+            Last Modified Low = 2716647936;
+            Last Modified High = 29449659;
+        }
+    }
+
+    Wave
+    {
+        Name = bg2;
+        File = bg2.wav;
+        Build Settings Last Modified Low = 673905748;
+        Build Settings Last Modified High = 29970206;
+
+        Cache
+        {
+            Format Tag = 0;
+            Channels = 1;
+            Sampling Rate = 44100;
+            Bits Per Sample = 1;
+            Play Region Offset = 44;
+            Play Region Length = 35092;
+            Loop Region Offset = 0;
+            Loop Region Length = 0;
+            File Type = 1;
+            Last Modified Low = 1892429824;
+            Last Modified High = 29450906;
+        }
+    }
+
+    Wave
+    {
+        Name = bg3;
+        File = bg3.wav;
+        Build Settings Last Modified Low = 673905748;
+        Build Settings Last Modified High = 29970206;
+
+        Cache
+        {
+            Format Tag = 0;
+            Channels = 1;
+            Sampling Rate = 44100;
+            Bits Per Sample = 1;
+            Play Region Offset = 44;
+            Play Region Length = 35188;
+            Loop Region Offset = 0;
+            Loop Region Length = 0;
+            File Type = 1;
+            Last Modified Low = 2492429824;
+            Last Modified High = 29450906;
+        }
+    }
+
+    Wave
+    {
+        Name = bgghost;
+        File = bgghost.wav;
+        Build Settings Last Modified Low = 673915748;
+        Build Settings Last Modified High = 29970206;
+
+        Cache
+        {
+            Format Tag = 0;
+            Channels = 1;
+            Sampling Rate = 44100;
+            Bits Per Sample = 1;
+            Play Region Offset = 44;
+            Play Region Length = 13404;
+            Loop Region Offset = 0;
+            Loop Region Length = 0;
+            File Type = 1;
+            Last Modified Low = 597462528;
+            Last Modified High = 29450907;
+        }
+    }
+
+    Wave
+    {
+        Name = bgghosteyes;
+        File = bgghosteyes.wav;
+        Build Settings Last Modified Low = 673915748;
+        Build Settings Last Modified High = 29970206;
+
+        Cache
+        {
+            Format Tag = 0;
+            Channels = 1;
+            Sampling Rate = 44100;
+            Bits Per Sample = 1;
+            Play Region Offset = 44;
+            Play Region Length = 22194;
+            Loop Region Offset = 0;
+            Loop Region Length = 0;
+            File Type = 1;
+            Last Modified Low = 1102495232;
+            Last Modified High = 29450908;
+        }
+    }
+
+    Wave
+    {
+        Name = eatfruit;
+        File = eatfruit.wav;
+        Build Settings Last Modified Low = 673925748;
+        Build Settings Last Modified High = 29970206;
+
+        Cache
+        {
+            Format Tag = 0;
+            Channels = 1;
+            Sampling Rate = 11025;
+            Bits Per Sample = 0;
+            Play Region Offset = 44;
+            Play Region Length = 4845;
+            Loop Region Offset = 0;
+            Loop Region Length = 0;
+            File Type = 1;
+            Last Modified Low = 3835401216;
+            Last Modified High = 29171979;
+        }
+    }
+
+    Wave
+    {
+        Name = eatghost;
+        File = eatghost.wav;
+        Build Settings Last Modified Low = 673925748;
+        Build Settings Last Modified High = 29970206;
+
+        Cache
+        {
+            Format Tag = 0;
+            Channels = 1;
+            Sampling Rate = 16000;
+            Bits Per Sample = 0;
+            Play Region Offset = 44;
+            Play Region Length = 9046;
+            Loop Region Offset = 0;
+            Loop Region Length = 0;
+            File Type = 1;
+            Last Modified Low = 3617960960;
+            Last Modified High = 29175115;
+        }
+    }
+
+    Wave
+    {
+        Name = eatpill1;
+        File = eatpill1.wav;
+        Build Settings Last Modified Low = 673935748;
+        Build Settings Last Modified High = 29970206;
+
+        Cache
+        {
+            Format Tag = 0;
+            Channels = 1;
+            Sampling Rate = 44100;
+            Bits Per Sample = 1;
+            Play Region Offset = 44;
+            Play Region Length = 9082;
+            Loop Region Offset = 0;
+            Loop Region Length = 0;
+            File Type = 1;
+            Last Modified Low = 1870699520;
+            Last Modified High = 29449630;
+        }
+    }
+
+    Wave
+    {
+        Name = eatpill2;
+        File = eatpill2.wav;
+        Build Settings Last Modified Low = 673935748;
+        Build Settings Last Modified High = 29970206;
+
+        Cache
+        {
+            Format Tag = 0;
+            Channels = 1;
+            Sampling Rate = 44100;
+            Bits Per Sample = 1;
+            Play Region Offset = 44;
+            Play Region Length = 9018;
+            Loop Region Offset = 0;
+            Loop Region Length = 0;
+            File Type = 1;
+            Last Modified Low = 1870699520;
+            Last Modified High = 29449630;
+        }
+    }
+
+    Wave
+    {
+        Name = extralife;
+        File = extralife.wav;
+        Build Settings Last Modified Low = 673935748;
+        Build Settings Last Modified High = 29970206;
+
+        Cache
+        {
+            Format Tag = 0;
+            Channels = 1;
+            Sampling Rate = 11025;
+            Bits Per Sample = 0;
+            Play Region Offset = 44;
+            Play Region Length = 2224;
+            Loop Region Offset = 0;
+            Loop Region Length = 0;
+            File Type = 1;
+            Last Modified Low = 2302495232;
+            Last Modified High = 29450908;
+        }
+    }
+
+    Wave
+    {
+        Name = killed;
+        File = killed.wav;
+        Build Settings Last Modified Low = 673945748;
+        Build Settings Last Modified High = 29970206;
+
+        Cache
+        {
+            Format Tag = 0;
+            Channels = 1;
+            Sampling Rate = 11025;
+            Bits Per Sample = 0;
+            Play Region Offset = 92;
+            Play Region Length = 16916;
+            Loop Region Offset = 0;
+            Loop Region Length = 0;
+            File Type = 1;
+            Last Modified Low = 3835401216;
+            Last Modified High = 29171979;
+        }
+    }
+
+    Wave
+    {
+        Name = newgame;
+        File = newgame.wav;
+        Build Settings Last Modified Low = 673945748;
+        Build Settings Last Modified High = 29970206;
+
+        Cache
+        {
+            Format Tag = 0;
+            Channels = 1;
+            Sampling Rate = 11025;
+            Bits Per Sample = 0;
+            Play Region Offset = 44;
+            Play Region Length = 46490;
+            Loop Region Offset = 0;
+            Loop Region Length = 0;
+            File Type = 1;
+            Last Modified Low = 1433616896;
+            Last Modified High = 29448649;
+        }
+    }
+
+    Wave
+    {
+        Name = newlevel;
+        File = newlevel.wav;
+        Build Settings Last Modified Low = 673955748;
+        Build Settings Last Modified High = 29970206;
+
+        Cache
+        {
+            Format Tag = 0;
+            Channels = 1;
+            Sampling Rate = 11025;
+            Bits Per Sample = 0;
+            Play Region Offset = 44;
+            Play Region Length = 7087;
+            Loop Region Offset = 0;
+            Loop Region Length = 0;
+            File Type = 1;
+            Last Modified Low = 238124032;
+            Last Modified High = 29451233;
+        }
+    }
+}
+
+Sound Bank
+{
+    Name = Sound Bank;
+    Xbox File = Xbox\Sound Bank.xsb;
+    Windows File = Win\Sound Bank.xsb;
+    Xbox Bank Path Edited = 0;
+    Windows Bank Path Edited = 0;
+    Header Last Modified High = 0;
+    Header Last Modified Low = 0;
+
+    Sound
+    {
+        Name = newlevel;
+        Volume = -1200;
+        Pitch = 0;
+        Priority = 0;
+
+        Category Entry
+        {
+            Name = Default;
+        }
+
+        Track
+        {
+            Volume = 0;
+            Use Filter = 0;
+
+            Play Wave Event
+            {
+                Break Loop = 0;
+                Use Speaker Position = 0;
+                Use Center Speaker = 1;
+                New Speaker Position On Loop = 1;
+                Speaker Position Angle = 0.000000;
+                Speaker Position Arc = 0.000000;
+
+                Event Header
+                {
+                    Timestamp = 0;
+                    Relative = 0;
+                    Random Recurrence = 0;
+                    Random Offset = 0;
+                }
+
+                Wave Entry
+                {
+                    Bank Name = Wave Bank;
+                    Bank Index = 0;
+                    Entry Name = newlevel;
+                    Entry Index = 12;
+                    Weight = 255;
+                    Weight Min = 0;
+                }
+            }
+        }
+    }
+
+    Sound
+    {
+        Name = bg1;
+        Volume = -1200;
+        Pitch = 0;
+        Priority = 0;
+
+        Category Entry
+        {
+            Name = Default;
+        }
+
+        Track
+        {
+            Volume = 0;
+            Use Filter = 0;
+
+            Play Wave Event
+            {
+                Loop Count = 255;
+                Break Loop = 0;
+                Use Speaker Position = 0;
+                Use Center Speaker = 1;
+                New Speaker Position On Loop = 1;
+                Speaker Position Angle = 0.000000;
+                Speaker Position Arc = 0.000000;
+
+                Event Header
+                {
+                    Timestamp = 0;
+                    Relative = 0;
+                    Random Recurrence = 0;
+                    Random Offset = 0;
+                }
+
+                Wave Entry
+                {
+                    Bank Name = Wave Bank;
+                    Bank Index = 0;
+                    Entry Name = bg1;
+                    Entry Index = 0;
+                    Weight = 255;
+                    Weight Min = 0;
+                }
+            }
+        }
+    }
+
+    Sound
+    {
+        Name = bg2;
+        Volume = -1200;
+        Pitch = 0;
+        Priority = 0;
+
+        Category Entry
+        {
+            Name = Default;
+        }
+
+        Track
+        {
+            Volume = 0;
+            Use Filter = 0;
+
+            Play Wave Event
+            {
+                Loop Count = 255;
+                Break Loop = 0;
+                Use Speaker Position = 0;
+                Use Center Speaker = 1;
+                New Speaker Position On Loop = 1;
+                Speaker Position Angle = 0.000000;
+                Speaker Position Arc = 0.000000;
+
+                Event Header
+                {
+                    Timestamp = 0;
+                    Relative = 0;
+                    Random Recurrence = 0;
+                    Random Offset = 0;
+                }
+
+                Wave Entry
+                {
+                    Bank Name = Wave Bank;
+                    Bank Index = 0;
+                    Entry Name = bg2;
+                    Entry Index = 1;
+                    Weight = 255;
+                    Weight Min = 0;
+                }
+            }
+        }
+    }
+
+    Sound
+    {
+        Name = bg3;
+        Volume = -1200;
+        Pitch = 0;
+        Priority = 0;
+
+        Category Entry
+        {
+            Name = Default;
+        }
+
+        Track
+        {
+            Volume = 0;
+            Use Filter = 0;
+
+            Play Wave Event
+            {
+                Loop Count = 255;
+                Break Loop = 0;
+                Use Speaker Position = 0;
+                Use Center Speaker = 1;
+                New Speaker Position On Loop = 1;
+                Speaker Position Angle = 0.000000;
+                Speaker Position Arc = 0.000000;
+
+                Event Header
+                {
+                    Timestamp = 0;
+                    Relative = 0;
+                    Random Recurrence = 0;
+                    Random Offset = 0;
+                }
+
+                Wave Entry
+                {
+                    Bank Name = Wave Bank;
+                    Bank Index = 0;
+                    Entry Name = bg3;
+                    Entry Index = 2;
+                    Weight = 255;
+                    Weight Min = 0;
+                }
+            }
+        }
+    }
+
+    Sound
+    {
+        Name = bgghost;
+        Volume = -1200;
+        Pitch = 0;
+        Priority = 0;
+
+        Category Entry
+        {
+            Name = Default;
+        }
+
+        Track
+        {
+            Volume = 0;
+            Use Filter = 0;
+
+            Play Wave Event
+            {
+                Loop Count = 255;
+                Break Loop = 0;
+                Use Speaker Position = 0;
+                Use Center Speaker = 1;
+                New Speaker Position On Loop = 1;
+                Speaker Position Angle = 0.000000;
+                Speaker Position Arc = 0.000000;
+
+                Event Header
+                {
+                    Timestamp = 0;
+                    Relative = 0;
+                    Random Recurrence = 0;
+                    Random Offset = 0;
+                }
+
+                Wave Entry
+                {
+                    Bank Name = Wave Bank;
+                    Bank Index = 0;
+                    Entry Name = bgghost;
+                    Entry Index = 3;
+                    Weight = 255;
+                    Weight Min = 0;
+                }
+            }
+        }
+    }
+
+    Sound
+    {
+        Name = bgghosteyes;
+        Volume = -1200;
+        Pitch = 0;
+        Priority = 0;
+
+        Category Entry
+        {
+            Name = Default;
+        }
+
+        Track
+        {
+            Volume = 0;
+            Use Filter = 0;
+
+            Play Wave Event
+            {
+                Loop Count = 255;
+                Break Loop = 0;
+                Use Speaker Position = 0;
+                Use Center Speaker = 1;
+                New Speaker Position On Loop = 1;
+                Speaker Position Angle = 0.000000;
+                Speaker Position Arc = 0.000000;
+
+                Event Header
+                {
+                    Timestamp = 0;
+                    Relative = 0;
+                    Random Recurrence = 0;
+                    Random Offset = 0;
+                }
+
+                Wave Entry
+                {
+                    Bank Name = Wave Bank;
+                    Bank Index = 0;
+                    Entry Name = bgghosteyes;
+                    Entry Index = 4;
+                    Weight = 255;
+                    Weight Min = 0;
+                }
+            }
+        }
+    }
+
+    Sound
+    {
+        Name = eatfruit;
+        Volume = -1200;
+        Pitch = 0;
+        Priority = 0;
+
+        Category Entry
+        {
+            Name = Default;
+        }
+
+        Track
+        {
+            Volume = 0;
+            Use Filter = 0;
+
+            Play Wave Event
+            {
+                Break Loop = 0;
+                Use Speaker Position = 0;
+                Use Center Speaker = 1;
+                New Speaker Position On Loop = 1;
+                Speaker Position Angle = 0.000000;
+                Speaker Position Arc = 0.000000;
+
+                Event Header
+                {
+                    Timestamp = 0;
+                    Relative = 0;
+                    Random Recurrence = 0;
+                    Random Offset = 0;
+                }
+
+                Wave Entry
+                {
+                    Bank Name = Wave Bank;
+                    Bank Index = 0;
+                    Entry Name = eatfruit;
+                    Entry Index = 5;
+                    Weight = 255;
+                    Weight Min = 0;
+                }
+            }
+        }
+    }
+
+    Sound
+    {
+        Name = eatghost;
+        Volume = -1200;
+        Pitch = 0;
+        Priority = 0;
+
+        Category Entry
+        {
+            Name = Default;
+        }
+
+        Track
+        {
+            Volume = 0;
+            Use Filter = 0;
+
+            Play Wave Event
+            {
+                Break Loop = 0;
+                Use Speaker Position = 0;
+                Use Center Speaker = 1;
+                New Speaker Position On Loop = 1;
+                Speaker Position Angle = 0.000000;
+                Speaker Position Arc = 0.000000;
+
+                Event Header
+                {
+                    Timestamp = 0;
+                    Relative = 0;
+                    Random Recurrence = 0;
+                    Random Offset = 0;
+                }
+
+                Wave Entry
+                {
+                    Bank Name = Wave Bank;
+                    Bank Index = 0;
+                    Entry Name = eatghost;
+                    Entry Index = 6;
+                    Weight = 255;
+                    Weight Min = 0;
+                }
+            }
+        }
+    }
+
+    Sound
+    {
+        Name = eatpill1;
+        Volume = -1200;
+        Pitch = 0;
+        Priority = 0;
+
+        Category Entry
+        {
+            Name = Default;
+        }
+
+        Track
+        {
+            Volume = 0;
+            Use Filter = 0;
+
+            Play Wave Event
+            {
+                Break Loop = 0;
+                Use Speaker Position = 0;
+                Use Center Speaker = 1;
+                New Speaker Position On Loop = 1;
+                Speaker Position Angle = 0.000000;
+                Speaker Position Arc = 0.000000;
+
+                Event Header
+                {
+                    Timestamp = 0;
+                    Relative = 0;
+                    Random Recurrence = 0;
+                    Random Offset = 0;
+                }
+
+                Wave Entry
+                {
+                    Bank Name = Wave Bank;
+                    Bank Index = 0;
+                    Entry Name = eatpill1;
+                    Entry Index = 7;
+                    Weight = 255;
+                    Weight Min = 0;
+                }
+            }
+        }
+    }
+
+    Sound
+    {
+        Name = eatpill2;
+        Volume = -1200;
+        Pitch = 0;
+        Priority = 0;
+
+        Category Entry
+        {
+            Name = Default;
+        }
+
+        Track
+        {
+            Volume = 0;
+            Use Filter = 0;
+
+            Play Wave Event
+            {
+                Break Loop = 0;
+                Use Speaker Position = 0;
+                Use Center Speaker = 1;
+                New Speaker Position On Loop = 1;
+                Speaker Position Angle = 0.000000;
+                Speaker Position Arc = 0.000000;
+
+                Event Header
+                {
+                    Timestamp = 0;
+                    Relative = 0;
+                    Random Recurrence = 0;
+                    Random Offset = 0;
+                }
+
+                Wave Entry
+                {
+                    Bank Name = Wave Bank;
+                    Bank Index = 0;
+                    Entry Name = eatpill2;
+                    Entry Index = 8;
+                    Weight = 255;
+                    Weight Min = 0;
+                }
+            }
+        }
+    }
+
+    Sound
+    {
+        Name = extralife;
+        Volume = -1200;
+        Pitch = 0;
+        Priority = 0;
+
+        Category Entry
+        {
+            Name = Default;
+        }
+
+        Track
+        {
+            Volume = 0;
+            Use Filter = 0;
+
+            Play Wave Event
+            {
+                Break Loop = 0;
+                Use Speaker Position = 0;
+                Use Center Speaker = 1;
+                New Speaker Position On Loop = 1;
+                Speaker Position Angle = 0.000000;
+                Speaker Position Arc = 0.000000;
+
+                Event Header
+                {
+                    Timestamp = 0;
+                    Relative = 0;
+                    Random Recurrence = 0;
+                    Random Offset = 0;
+                }
+
+                Wave Entry
+                {
+                    Bank Name = Wave Bank;
+                    Bank Index = 0;
+                    Entry Name = extralife;
+                    Entry Index = 9;
+                    Weight = 255;
+                    Weight Min = 0;
+                }
+            }
+        }
+    }
+
+    Sound
+    {
+        Name = killed;
+        Volume = -1200;
+        Pitch = 0;
+        Priority = 0;
+
+        Category Entry
+        {
+            Name = Default;
+        }
+
+        Track
+        {
+            Volume = 0;
+            Use Filter = 0;
+
+            Play Wave Event
+            {
+                Break Loop = 0;
+                Use Speaker Position = 0;
+                Use Center Speaker = 1;
+                New Speaker Position On Loop = 1;
+                Speaker Position Angle = 0.000000;
+                Speaker Position Arc = 0.000000;
+
+                Event Header
+                {
+                    Timestamp = 0;
+                    Relative = 0;
+                    Random Recurrence = 0;
+                    Random Offset = 0;
+                }
+
+                Wave Entry
+                {
+                    Bank Name = Wave Bank;
+                    Bank Index = 0;
+                    Entry Name = killed;
+                    Entry Index = 10;
+                    Weight = 255;
+                    Weight Min = 0;
+                }
+            }
+        }
+    }
+
+    Sound
+    {
+        Name = newgame;
+        Volume = -1200;
+        Pitch = 0;
+        Priority = 0;
+
+        Category Entry
+        {
+            Name = Default;
+        }
+
+        Track
+        {
+            Volume = 0;
+            Use Filter = 0;
+
+            Play Wave Event
+            {
+                Break Loop = 0;
+                Use Speaker Position = 0;
+                Use Center Speaker = 1;
+                New Speaker Position On Loop = 1;
+                Speaker Position Angle = 0.000000;
+                Speaker Position Arc = 0.000000;
+
+                Event Header
+                {
+                    Timestamp = 0;
+                    Relative = 0;
+                    Random Recurrence = 0;
+                    Random Offset = 0;
+                }
+
+                Wave Entry
+                {
+                    Bank Name = Wave Bank;
+                    Bank Index = 0;
+                    Entry Name = newgame;
+                    Entry Index = 11;
+                    Weight = 255;
+                    Weight Min = 0;
+                }
+            }
+        }
+    }
+
+    Cue
+    {
+        Name = NewLevel;
+
+        Variation
+        {
+            Variation Type = 3;
+            Variation Table Type = 1;
+            New Variation on Loop = 0;
+        }
+
+        Sound Entry
+        {
+            Name = newlevel;
+            Index = 0;
+            Weight Min = 0;
+            Weight Max = 255;
+        }
+    }
+
+    Cue
+    {
+        Name = GhostNormalLoop1;
+
+        Variation
+        {
+            Variation Type = 3;
+            Variation Table Type = 1;
+            New Variation on Loop = 0;
+        }
+
+        Sound Entry
+        {
+            Name = bg1;
+            Index = 1;
+            Weight Min = 0;
+            Weight Max = 252;
+        }
+    }
+
+    Cue
+    {
+        Name = GhostFastLoop;
+
+        Variation
+        {
+            Variation Type = 3;
+            Variation Table Type = 1;
+            New Variation on Loop = 0;
+        }
+
+        Sound Entry
+        {
+            Name = bg2;
+            Index = 2;
+            Weight Min = 0;
+            Weight Max = 255;
+        }
+    }
+
+    Cue
+    {
+        Name = GhostVFastLoop;
+
+        Variation
+        {
+            Variation Type = 3;
+            Variation Table Type = 1;
+            New Variation on Loop = 0;
+        }
+
+        Sound Entry
+        {
+            Name = bg3;
+            Index = 3;
+            Weight Min = 0;
+            Weight Max = 255;
+        }
+    }
+
+    Cue
+    {
+        Name = GhostChased;
+
+        Variation
+        {
+            Variation Type = 3;
+            Variation Table Type = 1;
+            New Variation on Loop = 0;
+        }
+
+        Sound Entry
+        {
+            Name = bgghost;
+            Index = 4;
+            Weight Min = 0;
+            Weight Max = 255;
+        }
+    }
+
+    Cue
+    {
+        Name = GhostRunningHome;
+
+        Variation
+        {
+            Variation Type = 3;
+            Variation Table Type = 1;
+            New Variation on Loop = 0;
+        }
+
+        Sound Entry
+        {
+            Name = bgghosteyes;
+            Index = 5;
+            Weight Min = 0;
+            Weight Max = 255;
+        }
+    }
+
+    Cue
+    {
+        Name = fruiteat;
+
+        Variation
+        {
+            Variation Type = 3;
+            Variation Table Type = 1;
+            New Variation on Loop = 0;
+        }
+
+        Sound Entry
+        {
+            Name = eatfruit;
+            Index = 6;
+            Weight Min = 0;
+            Weight Max = 255;
+        }
+    }
+
+    Cue
+    {
+        Name = EatGhost;
+
+        Variation
+        {
+            Variation Type = 3;
+            Variation Table Type = 1;
+            New Variation on Loop = 0;
+        }
+
+        Sound Entry
+        {
+            Name = eatghost;
+            Index = 7;
+            Weight Min = 0;
+            Weight Max = 255;
+        }
+    }
+
+    Cue
+    {
+        Name = PacMAnEat1;
+
+        Variation
+        {
+            Variation Type = 3;
+            Variation Table Type = 1;
+            New Variation on Loop = 0;
+        }
+
+        Sound Entry
+        {
+            Name = eatpill1;
+            Index = 8;
+            Weight Min = 0;
+            Weight Max = 255;
+        }
+    }
+
+    Cue
+    {
+        Name = PacManEat2;
+
+        Variation
+        {
+            Variation Type = 3;
+            Variation Table Type = 1;
+            New Variation on Loop = 0;
+        }
+
+        Sound Entry
+        {
+            Name = eatpill2;
+            Index = 9;
+            Weight Min = 0;
+            Weight Max = 255;
+        }
+    }
+
+    Cue
+    {
+        Name = ExtraLife;
+
+        Variation
+        {
+            Variation Type = 3;
+            Variation Table Type = 1;
+            New Variation on Loop = 0;
+        }
+
+        Sound Entry
+        {
+            Name = extralife;
+            Index = 10;
+            Weight Min = 0;
+            Weight Max = 255;
+        }
+    }
+
+    Cue
+    {
+        Name = Death;
+
+        Variation
+        {
+            Variation Type = 3;
+            Variation Table Type = 1;
+            New Variation on Loop = 0;
+        }
+
+        Sound Entry
+        {
+            Name = killed;
+            Index = 11;
+            Weight Min = 0;
+            Weight Max = 255;
+        }
+    }
+
+    Cue
+    {
+        Name = Intro;
+
+        Variation
+        {
+            Variation Type = 3;
+            Variation Table Type = 1;
+            New Variation on Loop = 0;
+        }
+
+        Sound Entry
+        {
+            Name = newgame;
+            Index = 12;
+            Weight Min = 0;
+            Weight Max = 255;
+        }
+    }
+}

+ 322 - 0
Samples/MacOS/XNAPacMan/Content/Audio/XNAPacManAudio.xml

@@ -0,0 +1,322 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Sections>
+  <WaveBank Name="Wave Bank">Wave Bank
+{
+    Name = Wave Bank;
+    Xbox File = Xbox\Wave Bank.xwb;
+    Windows File = Win\Wave Bank.xwb;
+    Xbox Bank Path Edited = 0;
+    Windows Bank Path Edited = 0;
+    Seek Tables = 1;
+    Compression Preset Name = &lt;none&gt;;
+    Xbox Bank Last Modified Low = 0;
+    Xbox Bank Last Modified High = 0;
+    PC Bank Last Modified Low = 0;
+    PC Bank Last Modified High = 0;
+    Header Last Modified Low = 0;
+    Header Last Modified High = 0;
+    Bank Last Revised Low = 711905748;
+    Bank Last Revised High = 29970206;
+
+    Wave
+    {
+        Name = bg1;
+        File = bg1.wav;
+        Build Settings Last Modified Low = 673905748;
+        Build Settings Last Modified High = 29970206;
+
+        Cache
+        {
+            Format Tag = 0;
+            Channels = 1;
+            Sampling Rate = 44100;
+            Bits Per Sample = 1;
+            Play Region Offset = 44;
+            Play Region Length = 35162;
+            Loop Region Offset = 0;
+            Loop Region Length = 0;
+            File Type = 1;
+            Last Modified Low = 2716647936;
+            Last Modified High = 29449659;
+        }
+    }
+
+    Wave
+    {
+        Name = bg2;
+        File = bg2.wav;
+        Build Settings Last Modified Low = 673905748;
+        Build Settings Last Modified High = 29970206;
+
+        Cache
+        {
+            Format Tag = 0;
+            Channels = 1;
+            Sampling Rate = 44100;
+            Bits Per Sample = 1;
+            Play Region Offset = 44;
+            Play Region Length = 35092;
+            Loop Region Offset = 0;
+            Loop Region Length = 0;
+            File Type = 1;
+            Last Modified Low = 1892429824;
+            Last Modified High = 29450906;
+        }
+    }
+
+    Wave
+    {
+        Name = bg3;
+        File = bg3.wav;
+        Build Settings Last Modified Low = 673905748;
+        Build Settings Last Modified High = 29970206;
+
+        Cache
+        {
+            Format Tag = 0;
+            Channels = 1;
+            Sampling Rate = 44100;
+            Bits Per Sample = 1;
+            Play Region Offset = 44;
+            Play Region Length = 35188;
+            Loop Region Offset = 0;
+            Loop Region Length = 0;
+            File Type = 1;
+            Last Modified Low = 2492429824;
+            Last Modified High = 29450906;
+        }
+    }
+
+    Wave
+    {
+        Name = bgghost;
+        File = bgghost.wav;
+        Build Settings Last Modified Low = 673915748;
+        Build Settings Last Modified High = 29970206;
+
+        Cache
+        {
+            Format Tag = 0;
+            Channels = 1;
+            Sampling Rate = 44100;
+            Bits Per Sample = 1;
+            Play Region Offset = 44;
+            Play Region Length = 13404;
+            Loop Region Offset = 0;
+            Loop Region Length = 0;
+            File Type = 1;
+            Last Modified Low = 597462528;
+            Last Modified High = 29450907;
+        }
+    }
+
+    Wave
+    {
+        Name = bgghosteyes;
+        File = bgghosteyes.wav;
+        Build Settings Last Modified Low = 673915748;
+        Build Settings Last Modified High = 29970206;
+
+        Cache
+        {
+            Format Tag = 0;
+            Channels = 1;
+            Sampling Rate = 44100;
+            Bits Per Sample = 1;
+            Play Region Offset = 44;
+            Play Region Length = 22194;
+            Loop Region Offset = 0;
+            Loop Region Length = 0;
+            File Type = 1;
+            Last Modified Low = 1102495232;
+            Last Modified High = 29450908;
+        }
+    }
+
+    Wave
+    {
+        Name = eatfruit;
+        File = eatfruit.wav;
+        Build Settings Last Modified Low = 673925748;
+        Build Settings Last Modified High = 29970206;
+
+        Cache
+        {
+            Format Tag = 0;
+            Channels = 1;
+            Sampling Rate = 11025;
+            Bits Per Sample = 0;
+            Play Region Offset = 44;
+            Play Region Length = 4845;
+            Loop Region Offset = 0;
+            Loop Region Length = 0;
+            File Type = 1;
+            Last Modified Low = 3835401216;
+            Last Modified High = 29171979;
+        }
+    }
+
+    Wave
+    {
+        Name = eatghost;
+        File = eatghost.wav;
+        Build Settings Last Modified Low = 673925748;
+        Build Settings Last Modified High = 29970206;
+
+        Cache
+        {
+            Format Tag = 0;
+            Channels = 1;
+            Sampling Rate = 16000;
+            Bits Per Sample = 0;
+            Play Region Offset = 44;
+            Play Region Length = 9046;
+            Loop Region Offset = 0;
+            Loop Region Length = 0;
+            File Type = 1;
+            Last Modified Low = 3617960960;
+            Last Modified High = 29175115;
+        }
+    }
+
+    Wave
+    {
+        Name = eatpill1;
+        File = eatpill1.wav;
+        Build Settings Last Modified Low = 673935748;
+        Build Settings Last Modified High = 29970206;
+
+        Cache
+        {
+            Format Tag = 0;
+            Channels = 1;
+            Sampling Rate = 44100;
+            Bits Per Sample = 1;
+            Play Region Offset = 44;
+            Play Region Length = 9082;
+            Loop Region Offset = 0;
+            Loop Region Length = 0;
+            File Type = 1;
+            Last Modified Low = 1870699520;
+            Last Modified High = 29449630;
+        }
+    }
+
+    Wave
+    {
+        Name = eatpill2;
+        File = eatpill2.wav;
+        Build Settings Last Modified Low = 673935748;
+        Build Settings Last Modified High = 29970206;
+
+        Cache
+        {
+            Format Tag = 0;
+            Channels = 1;
+            Sampling Rate = 44100;
+            Bits Per Sample = 1;
+            Play Region Offset = 44;
+            Play Region Length = 9018;
+            Loop Region Offset = 0;
+            Loop Region Length = 0;
+            File Type = 1;
+            Last Modified Low = 1870699520;
+            Last Modified High = 29449630;
+        }
+    }
+
+    Wave
+    {
+        Name = extralife;
+        File = extralife.wav;
+        Build Settings Last Modified Low = 673935748;
+        Build Settings Last Modified High = 29970206;
+
+        Cache
+        {
+            Format Tag = 0;
+            Channels = 1;
+            Sampling Rate = 11025;
+            Bits Per Sample = 0;
+            Play Region Offset = 44;
+            Play Region Length = 2224;
+            Loop Region Offset = 0;
+            Loop Region Length = 0;
+            File Type = 1;
+            Last Modified Low = 2302495232;
+            Last Modified High = 29450908;
+        }
+    }
+
+    Wave
+    {
+        Name = killed;
+        File = killed.wav;
+        Build Settings Last Modified Low = 673945748;
+        Build Settings Last Modified High = 29970206;
+
+        Cache
+        {
+            Format Tag = 0;
+            Channels = 1;
+            Sampling Rate = 11025;
+            Bits Per Sample = 0;
+            Play Region Offset = 92;
+            Play Region Length = 16916;
+            Loop Region Offset = 0;
+            Loop Region Length = 0;
+            File Type = 1;
+            Last Modified Low = 3835401216;
+            Last Modified High = 29171979;
+        }
+    }
+
+    Wave
+    {
+        Name = newgame;
+        File = newgame.wav;
+        Build Settings Last Modified Low = 673945748;
+        Build Settings Last Modified High = 29970206;
+
+        Cache
+        {
+            Format Tag = 0;
+            Channels = 1;
+            Sampling Rate = 11025;
+            Bits Per Sample = 0;
+            Play Region Offset = 44;
+            Play Region Length = 46490;
+            Loop Region Offset = 0;
+            Loop Region Length = 0;
+            File Type = 1;
+            Last Modified Low = 1433616896;
+            Last Modified High = 29448649;
+        }
+    }
+
+    Wave
+    {
+        Name = newlevel;
+        File = newlevel.wav;
+        Build Settings Last Modified Low = 673955748;
+        Build Settings Last Modified High = 29970206;
+
+        Cache
+        {
+            Format Tag = 0;
+            Channels = 1;
+            Sampling Rate = 11025;
+            Bits Per Sample = 0;
+            Play Region Offset = 44;
+            Play Region Length = 7087;
+            Loop Region Offset = 0;
+            Loop Region Length = 0;
+            File Type = 1;
+            Last Modified Low = 238124032;
+            Last Modified High = 29451233;
+        }
+    }
+}
+
+</WaveBank>
+</Sections>

BIN
Samples/MacOS/XNAPacMan/Content/Audio/YEPAudio.xgs


BIN
Samples/MacOS/XNAPacMan/Content/Audio/bg1.wav


BIN
Samples/MacOS/XNAPacMan/Content/Audio/bg2.wav


BIN
Samples/MacOS/XNAPacMan/Content/Audio/bg3.wav


BIN
Samples/MacOS/XNAPacMan/Content/Audio/bgghost.wav


BIN
Samples/MacOS/XNAPacMan/Content/Audio/bgghosteyes.wav


BIN
Samples/MacOS/XNAPacMan/Content/Audio/eatfruit.wav


BIN
Samples/MacOS/XNAPacMan/Content/Audio/eatghost.wav


BIN
Samples/MacOS/XNAPacMan/Content/Audio/eatpill1.wav


BIN
Samples/MacOS/XNAPacMan/Content/Audio/eatpill2.wav


BIN
Samples/MacOS/XNAPacMan/Content/Audio/extralife.wav


BIN
Samples/MacOS/XNAPacMan/Content/Audio/killed.wav


BIN
Samples/MacOS/XNAPacMan/Content/Audio/newgame.wav


BIN
Samples/MacOS/XNAPacMan/Content/Audio/newlevel.wav


BIN
Samples/MacOS/XNAPacMan/Content/Grid.txt


BIN
Samples/MacOS/XNAPacMan/Content/MenuItem.xnb


BIN
Samples/MacOS/XNAPacMan/Content/Score.xnb


BIN
Samples/MacOS/XNAPacMan/Content/ScoreEvent.xnb


BIN
Samples/MacOS/XNAPacMan/Content/bonus/Apple.png


BIN
Samples/MacOS/XNAPacMan/Content/bonus/Apple.xnb


BIN
Samples/MacOS/XNAPacMan/Content/bonus/Banana.png


BIN
Samples/MacOS/XNAPacMan/Content/bonus/Banana.xnb


BIN
Samples/MacOS/XNAPacMan/Content/bonus/Bell.png


BIN
Samples/MacOS/XNAPacMan/Content/bonus/Bell.xnb


BIN
Samples/MacOS/XNAPacMan/Content/bonus/Cherry.png


BIN
Samples/MacOS/XNAPacMan/Content/bonus/Cherry.xnb


BIN
Samples/MacOS/XNAPacMan/Content/bonus/Key.png


BIN
Samples/MacOS/XNAPacMan/Content/bonus/Key.xnb


BIN
Samples/MacOS/XNAPacMan/Content/bonus/Orange.png


BIN
Samples/MacOS/XNAPacMan/Content/bonus/Orange.xnb


BIN
Samples/MacOS/XNAPacMan/Content/bonus/Pear.png


BIN
Samples/MacOS/XNAPacMan/Content/bonus/Pear.xnb


BIN
Samples/MacOS/XNAPacMan/Content/bonus/Pretzel.png


BIN
Samples/MacOS/XNAPacMan/Content/bonus/Pretzel.xnb


BIN
Samples/MacOS/XNAPacMan/Content/bonus/Strawberry.png


BIN
Samples/MacOS/XNAPacMan/Content/bonus/Strawberry.xnb


BIN
Samples/MacOS/XNAPacMan/Content/sprites/Board.png


BIN
Samples/MacOS/XNAPacMan/Content/sprites/Board.xnb


BIN
Samples/MacOS/XNAPacMan/Content/sprites/BoardFlash.png


BIN
Samples/MacOS/XNAPacMan/Content/sprites/BoardFlash.xnb


BIN
Samples/MacOS/XNAPacMan/Content/sprites/Crump.png


BIN
Samples/MacOS/XNAPacMan/Content/sprites/Crump.xnb


BIN
Samples/MacOS/XNAPacMan/Content/sprites/DyingSheetNew.png


BIN
Samples/MacOS/XNAPacMan/Content/sprites/DyingSheetNew.xnb


BIN
Samples/MacOS/XNAPacMan/Content/sprites/ExtraLife.png


BIN
Samples/MacOS/XNAPacMan/Content/sprites/ExtraLife.xnb


BIN
Samples/MacOS/XNAPacMan/Content/sprites/GhostBase.png


BIN
Samples/MacOS/XNAPacMan/Content/sprites/GhostBase.xnb


BIN
Samples/MacOS/XNAPacMan/Content/sprites/GhostBase2.png


BIN
Samples/MacOS/XNAPacMan/Content/sprites/GhostBase2.xnb


BIN
Samples/MacOS/XNAPacMan/Content/sprites/GhostChased.png


BIN
Samples/MacOS/XNAPacMan/Content/sprites/GhostChased.xnb


BIN
Samples/MacOS/XNAPacMan/Content/sprites/GhostEyes.png


BIN
Samples/MacOS/XNAPacMan/Content/sprites/GhostEyes.xnb


BIN
Samples/MacOS/XNAPacMan/Content/sprites/GhostEyesCenter.png


BIN
Samples/MacOS/XNAPacMan/Content/sprites/GhostEyesCenter.xnb


BIN
Samples/MacOS/XNAPacMan/Content/sprites/PacManEating1.png


BIN
Samples/MacOS/XNAPacMan/Content/sprites/PacManEating1.xnb


BIN
Samples/MacOS/XNAPacMan/Content/sprites/PacManEating2.png


BIN
Samples/MacOS/XNAPacMan/Content/sprites/PacManEating2.xnb


BIN
Samples/MacOS/XNAPacMan/Content/sprites/PacManEating3.png


BIN
Samples/MacOS/XNAPacMan/Content/sprites/PacManEating3.xnb


BIN
Samples/MacOS/XNAPacMan/Content/sprites/PacManEating4.png


BIN
Samples/MacOS/XNAPacMan/Content/sprites/PacManEating4.xnb


BIN
Samples/MacOS/XNAPacMan/Content/sprites/PacManEating5.png


BIN
Samples/MacOS/XNAPacMan/Content/sprites/PacManEating5.xnb


BIN
Samples/MacOS/XNAPacMan/Content/sprites/PacManEating6.png


BIN
Samples/MacOS/XNAPacMan/Content/sprites/PacManEating6.xnb


BIN
Samples/MacOS/XNAPacMan/Content/sprites/PacManEating7.png


BIN
Samples/MacOS/XNAPacMan/Content/sprites/PacManEating7.xnb


BIN
Samples/MacOS/XNAPacMan/Content/sprites/PacManEating8.png


BIN
Samples/MacOS/XNAPacMan/Content/sprites/PacManEating8.xnb


BIN
Samples/MacOS/XNAPacMan/Content/sprites/PacManEating9.png


BIN
Samples/MacOS/XNAPacMan/Content/sprites/PacManEating9.xnb


BIN
Samples/MacOS/XNAPacMan/Content/sprites/PowerPill.png


BIN
Samples/MacOS/XNAPacMan/Content/sprites/PowerPill.xnb


BIN
Samples/MacOS/XNAPacMan/Content/sprites/Selection.png


BIN
Samples/MacOS/XNAPacMan/Content/sprites/Selection.xnb


BIN
Samples/MacOS/XNAPacMan/Content/sprites/Title.png


BIN
Samples/MacOS/XNAPacMan/Content/sprites/Title.xnb


BIN
Samples/MacOS/XNAPacMan/Game.ico


+ 442 - 0
Samples/MacOS/XNAPacMan/GameLoop.cs

@@ -0,0 +1,442 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Audio;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+
+
+namespace XNAPacMan {
+
+    /// <summary>
+    /// Defines a position on the board where a ghost has died or a fruit was eaten, as well as the score earned.
+    /// This is used for knowing where to draw those scores
+    /// </summary>
+    struct ScoreEvent {
+        public ScoreEvent(Position position, DateTime when, int score) {
+            Position = position;
+            When = when;
+            Score = score;
+        }
+        public Position Position;
+        public DateTime When;
+        public int Score;
+    }
+    /// <summary>
+    /// GameLoop is the main "game" object; this is basically where the action
+    /// takes place. It's responsible for coordinating broad game logic,
+    /// drawing the board and scores, as well as linking with the menu.
+    /// </summary>
+    public class GameLoop : Microsoft.Xna.Framework.DrawableGameComponent {
+        public GameLoop(Game game)
+            : base(game) {
+            // TODO: Construct any child components here
+        }
+
+        /// <summary>
+        /// Allows the game component to perform any initialization it needs to before starting
+        /// to run.  This is where it can query for any required services and load content.
+        /// </summary>
+        public override void Initialize() {
+            // We don't want XNA calling this method each time we resume from the menu,
+            // unfortunately, it'll call it whatever we try. So the only thing
+            // we can do is check if it has been called already and return. Yes, it's ugly.
+            if (spriteBatch_ != null) {
+                GhostSoundsManager.ResumeLoops();
+                return;
+            }
+            // Otherwise, this is the first time this component is Initialized, so proceed.
+
+            GhostSoundsManager.Init(Game);
+
+            Grid.Reset();
+            Constants.Level = 1;
+            spriteBatch_ = (SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));
+            graphics_ = (GraphicsDeviceManager)Game.Services.GetService(typeof(GraphicsDeviceManager));
+            soundBank_ = (SoundBank)Game.Services.GetService(typeof(SoundBank));
+
+            scoreFont_ = Game.Content.Load<SpriteFont>("Score");
+            scoreEventFont_ = Game.Content.Load<SpriteFont>("ScoreEvent");
+            xlife_ = Game.Content.Load<Texture2D>("sprites/ExtraLife");
+            ppill_ = Game.Content.Load<Texture2D>("sprites/PowerPill");
+            crump_ = Game.Content.Load<Texture2D>("sprites/Crump");
+            board_ = Game.Content.Load<Texture2D>("sprites/Board");
+            boardFlash_ = Game.Content.Load<Texture2D>("sprites/BoardFlash");
+            bonusEaten_ = new Dictionary<string, int>();
+            bonus_ = new Dictionary<string, Texture2D>(9);
+            bonus_.Add("Apple", Game.Content.Load<Texture2D>("bonus/Apple"));
+            bonus_.Add("Banana", Game.Content.Load<Texture2D>("bonus/Banana"));
+            bonus_.Add("Bell", Game.Content.Load<Texture2D>("bonus/Bell"));
+            bonus_.Add("Cherry", Game.Content.Load<Texture2D>("bonus/Cherry"));
+            bonus_.Add("Key", Game.Content.Load<Texture2D>("bonus/Key"));
+            bonus_.Add("Orange", Game.Content.Load<Texture2D>("bonus/Orange"));
+            bonus_.Add("Pear", Game.Content.Load<Texture2D>("bonus/Pear"));
+            bonus_.Add("Pretzel", Game.Content.Load<Texture2D>("bonus/Pretzel"));
+            bonus_.Add("Strawberry", Game.Content.Load<Texture2D>("bonus/Strawberry"));
+
+            scoreEvents_ = new List<ScoreEvent>(5);
+            bonusPresent_ = false;
+            bonusSpawned_ = 0;
+            eatenGhosts_ = 0;
+            Score = 0;
+            xlives_ = 2;
+            paChomp_ = true;
+            playerDied_ = false;
+            player_ = new Player(Game);
+            ghosts_ = new List<Ghost> { new Ghost(Game, player_, Ghosts.Blinky), new Ghost(Game, player_, Ghosts.Clyde),
+                                        new Ghost(Game, player_, Ghosts.Inky), new Ghost(Game, player_, Ghosts.Pinky)};
+            ghosts_[2].SetBlinky(ghosts_[0]); // Oh, dirty hack. Inky needs this for his AI.
+            soundBank_.PlayCue("Intro");
+            LockTimer = TimeSpan.FromMilliseconds(4500);
+
+            base.Initialize();
+        }
+
+        /// <summary>
+        /// Allows the game component to update itself.
+        /// </summary>
+        /// <param name="gameTime">Provides a snapshot of timing values.</param>
+        public override void Update(GameTime gameTime) {
+
+            // Some events (death, new level, etc.) lock the game for a few moments.
+            if (DateTime.Now - eventTimer_ < LockTimer) {
+                ghosts_.ForEach(i => i.LockTimer(gameTime));
+                // Also we need to do the same thing for our own timer concerning bonuses
+                bonusSpawnedTime_ += gameTime.ElapsedGameTime;
+                return;
+            }
+
+            // Remove special events older than 5 seconds
+            scoreEvents_.RemoveAll(i => DateTime.Now - i.When > TimeSpan.FromSeconds(5));
+
+            // If the player had died, spawn a new one or end game.
+            if (playerDied_) {
+                // extra lives are decremented here, at the same time the pac man is spawned; this makes those 
+                // events seem linked.
+                xlives_--;
+                //xlives_++; // Give infinite lives to the evil developer;
+                if (xlives_ >= 0) {
+                    playerDied_ = false;
+                    player_ = new Player(Game);
+                    ghosts_.ForEach(i => i.Reset(false, player_));
+                    scoreEvents_.Clear();
+                }
+                else { // The game is over
+                    Menu.SaveHighScore(Score);
+                    Game.Components.Add(new Menu(Game, null));
+                    Game.Components.Remove(this);
+                    GhostSoundsManager.StopLoops();
+                    return;
+                }
+            }
+
+            // When all crumps have been eaten, wait a few seconds and then spawn a new level
+            if (noCrumpsLeft()) {
+                if (Constants.Level < 21) {
+                    bonusSpawned_ = 0;
+                    Grid.Reset();
+                    player_ = new Player(Game);
+                    ghosts_.ForEach(i => i.Reset(true, player_));
+                    soundBank_.PlayCue("NewLevel");
+                    LockTimer = TimeSpan.FromSeconds(2);
+                    Constants.Level++;
+                    return;
+                }
+                else { // Game over, you win.
+                    Menu.SaveHighScore(Score);
+                    Game.Components.Add(new Menu(Game, null));
+                    Game.Components.Remove(this);
+                    GhostSoundsManager.StopLoops();
+                    return;
+                }
+            }
+
+            Keys[] inputKeys = Keyboard.GetState().GetPressedKeys();
+            // The user may escape to the main menu with the escape key
+            if (inputKeys.Contains(Keys.Escape)) {
+                Game.Components.Add(new Menu(Game, this));
+                Game.Components.Remove(this);
+                GhostSoundsManager.PauseLoops(); // will be resumed in Initialize(). No need for stopping them
+                // if the player subsequently quits the game, since we'll re-initialize GhostSoundManager in
+                // Initialize() if the player wants to start a new game.
+                return;
+            }
+
+            // Eat crumps and power pills.
+            if (player_.Position.DeltaPixel == Point.Zero) {
+                Point playerTile = player_.Position.Tile;
+                if (Grid.TileGrid[playerTile.X, playerTile.Y].HasCrump) {
+                    soundBank_.PlayCue(paChomp_ ? "PacMAnEat1" : "PacManEat2");
+                    paChomp_ = !paChomp_;
+                    Score += 10;
+                    Grid.TileGrid[playerTile.X, playerTile.Y].HasCrump = false;
+                    if (Grid.TileGrid[playerTile.X, playerTile.Y].HasPowerPill) {
+                        Score += 40;
+                        eatenGhosts_ = 0;
+                        for (int i = 0; i < ghosts_.Count; i++) {
+                            if (ghosts_[i].State == GhostState.Attack || ghosts_[i].State == GhostState.Scatter ||
+                                ghosts_[i].State == GhostState.Blue) {
+                                ghosts_[i].State = GhostState.Blue;
+                            }
+                        }
+                        Grid.TileGrid[playerTile.X, playerTile.Y].HasPowerPill = false;
+                    }
+
+                    // If that was the last crump, lock the game for a while
+                    if (noCrumpsLeft()) {
+                        GhostSoundsManager.StopLoops();
+                        LockTimer = TimeSpan.FromSeconds(2);
+                        return;
+                    }
+                }
+            }
+
+            // Eat bonuses
+            if (bonusPresent_ && player_.Position.Tile.Y == 17 &&
+                ((player_.Position.Tile.X == 13 && player_.Position.DeltaPixel.X == 8) ||
+                  (player_.Position.Tile.X == 14 && player_.Position.DeltaPixel.X == -8))) {
+                LockTimer = TimeSpan.FromSeconds(1.5);
+                Score += Constants.BonusScores();
+                scoreEvents_.Add(new ScoreEvent(player_.Position, DateTime.Now, Constants.BonusScores()));
+                soundBank_.PlayCue("fruiteat");
+                bonusPresent_ = false;
+                if (bonusEaten_.ContainsKey(Constants.BonusSprite())) {
+                    bonusEaten_[Constants.BonusSprite()]++;
+                }
+                else {
+                    bonusEaten_.Add(Constants.BonusSprite(), 1);
+                }
+            }
+
+            // Remove bonus if time's up
+            if (bonusPresent_ && ((DateTime.Now - bonusSpawnedTime_) > TimeSpan.FromSeconds(10))) {
+                bonusPresent_ = false;
+            }
+
+            // Detect collision between ghosts and the player
+            foreach (Ghost ghost in ghosts_) {
+                Rectangle playerArea = new Rectangle((player_.Position.Tile.X * 16) + player_.Position.DeltaPixel.X,
+                                                     (player_.Position.Tile.Y * 16) + player_.Position.DeltaPixel.Y,
+                                                      26,
+                                                      26);
+                Rectangle ghostArea = new Rectangle((ghost.Position.Tile.X * 16) + ghost.Position.DeltaPixel.X,
+                                                    (ghost.Position.Tile.Y * 16) + ghost.Position.DeltaPixel.Y,
+                                                    22,
+                                                    22);
+                if (!Rectangle.Intersect(playerArea, ghostArea).IsEmpty) {
+                    // If collision detected, either kill the ghost or kill the pac man, depending on state.
+
+                    if (ghost.State == GhostState.Blue) {
+                        GhostSoundsManager.StopLoops();
+                        soundBank_.PlayCue("EatGhost");
+                        ghost.State = GhostState.Dead;
+                        eatenGhosts_++;
+                        int bonus = (int)(100 * Math.Pow(2, eatenGhosts_));
+                        Score += bonus;
+                        scoreEvents_.Add(new ScoreEvent(ghost.Position, DateTime.Now, bonus));
+                        LockTimer = TimeSpan.FromMilliseconds(900);
+                        return;
+                    }
+                    else if (ghost.State != GhostState.Dead ) {
+                        KillPacMan();
+                        return;
+                    }
+                    // Otherwise ( = the ghost is dead), don't do anything special.
+                }
+            }
+
+            // Periodically spawn a fruit, when the player isn't on the spawn location
+            // otherwise we get an infinite fruit spawning bug
+            if ((Grid.NumCrumps == 180 || Grid.NumCrumps == 80) && bonusSpawned_ < 2 &&
+                ! (player_.Position.Tile.Y == 17 &&
+                    ((player_.Position.Tile.X == 13 && player_.Position.DeltaPixel.X == 8) ||
+                    (player_.Position.Tile.X == 14 && player_.Position.DeltaPixel.X == -8)))) {
+                bonusPresent_ = true;
+                bonusSpawned_++;
+                bonusSpawnedTime_ = DateTime.Now;
+
+            }
+
+            // Now is the time to move player based on inputs and ghosts based on AI
+            // If we have returned earlier in the method, they stay in place
+            player_.Update(gameTime);
+            ghosts_.ForEach(i => i.Update(gameTime));
+
+            base.Update(gameTime);
+        }
+
+
+        /// <summary>
+        /// Nice to have for debug purposes. We might want the level to end early.
+        /// </summary>
+        /// <returns>Whether there are no crumps left on the board.</returns>
+        bool noCrumpsLeft() {
+            return Grid.NumCrumps == 0;
+        }
+
+
+        /// <summary>
+        /// AAAARRRGH
+        /// </summary>
+        void KillPacMan() {
+            player_.State = State.Dying;
+            GhostSoundsManager.StopLoops();
+            soundBank_.PlayCue("Death");
+            LockTimer = TimeSpan.FromMilliseconds(1811);
+            playerDied_ = true;
+            bonusPresent_ = false;
+            bonusSpawned_ = 0;
+        }
+
+        /// <summary>
+        /// This is called when the game should draw itself.
+        /// </summary>
+        /// <param name="gameTime">Provides a snapshot of timing values.</param>
+        public override void Draw(GameTime gameTime) {
+            base.Draw(gameTime);
+
+            // The GameLoop is a main component, so it is responsible for initializing the sprite batch each frame
+            spriteBatch_.Begin();
+
+            Vector2 boardPosition = new Vector2(
+                (graphics_.PreferredBackBufferWidth / 2) - (board_.Width / 2),
+                (graphics_.PreferredBackBufferHeight / 2) - (board_.Height / 2)
+                );
+
+            // When all crumps have been eaten, flash until new level is spawned
+            // Draw the player and nothing else, just end the spritebatch and return.
+            if (noCrumpsLeft()) {
+                spriteBatch_.Draw(((DateTime.Now.Second * 1000 + DateTime.Now.Millisecond) / 350) % 2 == 0 ? board_ : boardFlash_, boardPosition, Color.White);
+                player_.Draw(gameTime, boardPosition);
+                spriteBatch_.End();
+                return;
+            }
+            // Otherwise...
+
+            // Draw the board
+            spriteBatch_.Draw(board_, boardPosition, Color.White);
+
+            // Draw crumps and power pills
+            Tile[,] tiles = Grid.TileGrid;
+            for (int j = 0; j < Grid.Height; j++) {
+                for (int i = 0; i < Grid.Width; i++) {
+                    if (tiles[i, j].HasPowerPill) {
+                        spriteBatch_.Draw(ppill_, new Vector2(
+                            boardPosition.X + 3 + (i * 16),
+                            boardPosition.Y + 3 + (j * 16)),
+                            Color.White);
+                    }
+                    else if (tiles[i, j].HasCrump) {
+                        spriteBatch_.Draw(crump_, new Vector2(
+                            boardPosition.X + 5 + (i * 16),
+                            boardPosition.Y + 5 + (j * 16)),
+                            Color.White);
+                    }
+                }
+            }
+
+            // Draw extra lives; no more than 20 though
+            for (int i = 0; i < xlives_ && i < 20; i++) {
+                spriteBatch_.Draw(xlife_, new Vector2(boardPosition.X + 10 + (20 * i), board_.Height + boardPosition.Y + 10), Color.White);
+            }
+
+            // Draw current score
+            spriteBatch_.DrawString(scoreFont_, "SCORE", new Vector2(boardPosition.X + 30, boardPosition.Y - 50), Color.White);
+            spriteBatch_.DrawString(scoreFont_, Score.ToString(), new Vector2(boardPosition.X + 30, boardPosition.Y - 30), Color.White);
+
+            // Draw current level
+            spriteBatch_.DrawString(scoreFont_, "LEVEL", new Vector2(boardPosition.X + board_.Width - 80, boardPosition.Y - 50), Color.White);
+            spriteBatch_.DrawString(scoreFont_, Constants.Level.ToString(), new Vector2(boardPosition.X + board_.Width - 80, boardPosition.Y - 30), Color.White);
+
+            // Draw a bonus fruit if any
+            if (bonusPresent_) {
+                spriteBatch_.Draw(bonus_[Constants.BonusSprite()], new Vector2(boardPosition.X + (13 * 16) + 2, boardPosition.Y + (17 * 16) - 8), Color.White);
+            }
+
+            // Draw captured bonus fruits at the bottom of the screen
+            int k = 0;
+            foreach (KeyValuePair<string, int> kvp in bonusEaten_) {
+                for (int i = 0; i < kvp.Value; i++) {
+                    spriteBatch_.Draw(bonus_[kvp.Key], new Vector2(boardPosition.X + 10 + (22 * (k + i)), board_.Height + boardPosition.Y + 22), Color.White);
+                }
+                k += kvp.Value; 
+            }
+
+            // Draw ghosts
+            ghosts_.ForEach( i => i.Draw(gameTime, boardPosition));
+
+            // Draw player
+            player_.Draw(gameTime, boardPosition);
+
+            // Draw special scores (as when a ghost or fruit has been eaten)
+            foreach (ScoreEvent se in scoreEvents_) {
+                spriteBatch_.DrawString(scoreEventFont_, se.Score.ToString(), new Vector2(boardPosition.X + (se.Position.Tile.X * 16) + se.Position.DeltaPixel.X + 4,
+                                                                                           boardPosition.Y + (se.Position.Tile.Y * 16) + se.Position.DeltaPixel.Y + 4), Color.White);            
+            }
+
+            // Draw GET READY ! at level start
+            if (player_.State == State.Start) {
+                spriteBatch_.DrawString(scoreFont_, "GET READY!", new Vector2(boardPosition.X + (board_.Width / 2) - 58, boardPosition.Y + 273), Color.Yellow);
+            }
+
+            // Display number of crumps (for debug)
+            //spriteBatch_.DrawString(scoreFont_, "Crumps left :" + Grid.NumCrumps.ToString(), Vector2.Zero, Color.White);
+
+            spriteBatch_.End();
+        }
+
+
+
+        // DRAWING
+        Dictionary<string, Texture2D> bonus_;
+        Texture2D xlife_;
+        Texture2D board_;
+        Texture2D boardFlash_;
+        Texture2D crump_;
+        Texture2D ppill_;
+        SpriteFont scoreFont_;
+        SpriteFont scoreEventFont_;
+        SoundBank soundBank_;
+        GraphicsDeviceManager graphics_;
+        SpriteBatch spriteBatch_;
+
+        // LOGIC
+        List<Ghost> ghosts_;
+        Player player_;
+        TimeSpan lockTimer_;
+        DateTime eventTimer_;
+        int bonusSpawned_;
+        bool bonusPresent_;
+        DateTime bonusSpawnedTime_;
+        Dictionary<string, int> bonusEaten_;
+        bool playerDied_;
+        bool paChomp_;
+        int xlives_;
+        int score_;
+        int eatenGhosts_;
+        List<ScoreEvent> scoreEvents_;
+
+        /// <summary>
+        /// The player's current score.
+        /// </summary>
+        public int Score {
+            get { return score_; }
+            private set {
+                if ((value / 10000) > (score_ / 10000)) {
+                    soundBank_.PlayCue("ExtraLife");
+                    xlives_++;
+                }
+                score_ = value; 
+            }
+        }
+
+        /// <summary>
+        /// For how much time we want to lock the game.
+        /// </summary>
+        private TimeSpan LockTimer {
+            get { return lockTimer_; }
+            set { eventTimer_ = DateTime.Now; lockTimer_ = value; }
+        }
+    }
+}

BIN
Samples/MacOS/XNAPacMan/GameThumbnail.png


+ 747 - 0
Samples/MacOS/XNAPacMan/Ghost.cs

@@ -0,0 +1,747 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+
+
+namespace XNAPacMan {
+
+    public enum GhostState { Home, Scatter, Attack, Blue, Dead }
+    public enum Ghosts { Blinky, Pinky, Inky, Clyde }
+
+
+    /// <summary>
+    /// One of the ghosts that try to kill Pac Man. 
+    /// </summary>
+    public class Ghost {
+        /// <summary>
+        /// Instantiates a ghost.
+        /// </summary>
+        /// <param name="game">A reference to the Game object, needed for access to services.</param>
+        /// <param name="player">A reference to the Pac Man, needed for AI.</param>
+        /// <param name="identity">Which ghost, needed for appearance and behavior.</param>
+        public Ghost(Game game, Player player, Ghosts identity) {
+            spriteBatch_ = (SpriteBatch)game.Services.GetService(typeof(SpriteBatch));
+            ghostBase1_ = game.Content.Load<Texture2D>("sprites/GhostBase");
+            ghostBase2_ = game.Content.Load<Texture2D>("sprites/GhostBase2");
+            ghostChased_ = game.Content.Load<Texture2D>("sprites/GhostChased");
+            eyesBase_ = game.Content.Load<Texture2D>("sprites/GhostEyes");
+            eyesCenter_ = game.Content.Load<Texture2D>("sprites/GhostEyesCenter");
+            colorBase_ = Constants.colors(identity);
+            identity_ = identity;
+            previousNumCrumps_ = 0;
+            Reset(true, player);
+            wiggle_ = true;
+            direction_ = new Direction();
+            lastJunction_ = new Point();
+            scatterTiles_ = Constants.scatterTiles(identity);
+        }
+
+        /// <summary>
+        /// Put the ghosts back in their home, ready to begin a new attack
+        /// </summary>
+        /// <param name="newLevel">True at level start, false otherwise</param>
+        /// <param name="player">The pac man. Pac Man is often respawned with the ghosts, so they need to know about the new Pac Man.</param>
+        public void Reset(bool newLevel, Player player) {
+            State = GhostState.Home; // Ghosts start at home
+            previousState_ = GhostState.Home; // Sounds are played when currentState != previousState.
+            // So to get them playing at level start, we do this simple hack.
+            updateCount_ = 0;
+            initialJumps_ = Constants.InitialJumps(identity_, newLevel);
+            position_ = Constants.startPosition(identity_);
+            scheduleStateEval_ = true;
+            lastJunction_ = new Point();
+            player_ = player;
+            scatterModesLeft_ = 4;
+            UpdateSpeed();
+        }
+
+        /// <summary>
+        /// When we need to lock the game, make sure the timers here are updated to reflect
+        /// the "waste of time".
+        /// </summary>
+        public void LockTimer(GameTime gameTime) {
+            timeInCurrentState += gameTime.ElapsedGameTime;
+        }
+
+        /// <summary>
+        /// In case this ghost is Inky, he will need a reference to blinky in order
+        /// to find his way around the maze. Otherwise, setting this has no effect.
+        /// </summary>
+        /// <param name="blinky">A reference to Blinky.</param>
+        public void SetBlinky(Ghost blinky) {
+            blinky_ = blinky;
+        }
+
+        /// <summary>
+        /// Call this every game update to get those ghosts moving around.
+        /// </summary>
+        /// <param name="gameTime">Provides a snapshot of timing values.</param>
+        public void Update(GameTime gameTime) {
+            if (scheduleStateEval_) {
+                StateEval();
+            }
+            // After the AI has had the occasion to run, update lastJuntion.
+            if (position_.DeltaPixel == Point.Zero && IsAJunction(position_.Tile)) {
+                lastJunction_ = position_.Tile;
+            }
+            Move();
+        }
+
+
+        void PlaySound() {
+            if (State == GhostState.Attack || State == GhostState.Scatter || State == GhostState.Home) {
+                if (Grid.NumCrumps < 50) {
+                    GhostSoundsManager.playLoopAttackVeryFast();
+                } else if (Grid.NumCrumps < 120) {
+                    GhostSoundsManager.playLoopAttackFast();
+                } else {
+                    GhostSoundsManager.playLoopAttack();
+                }
+            } else if (State == GhostState.Blue) {
+                GhostSoundsManager.playLoopBlue();
+            } else if (State == GhostState.Dead) {
+                GhostSoundsManager.playLoopDead();
+            }
+        }
+
+        void StateEval() {
+
+            GhostState initialState = State;
+            scheduleStateEval_ = false;
+
+            switch (State) {
+                case GhostState.Home:
+                    // Ghost exit the home state for the scatter state when they get to the row
+                    // above the home
+                    if (position_.Tile.Y == 11 && position_.DeltaPixel.Y == 0) {
+                        // Select inital direction based on scatter tiles
+                        if (Constants.scatterTiles(identity_)[0].X < 13) {
+                            direction_ = Direction.Left;
+                        } else {
+                            direction_ = Direction.Right;
+                        }
+                        if (scatterModesLeft_ > 0) {
+                            State = GhostState.Scatter;
+                        } else {
+                            State = GhostState.Attack;
+                        }
+                        return;
+                    }
+                        // Ghosts move up when they are aligned with the entrance
+                    else if (position_.Tile.X == 13 && position_.DeltaPixel.X == 8) {
+                        direction_ = Direction.Up;
+                    }
+                        // When on one side, move towards middle when on the bottom and time's up
+                        // If time's not up, keep bouncing up and down
+                    else if ((position_.DeltaPixel.Y == 8) &&
+                            ((position_.Tile.X == 11 && position_.DeltaPixel.X == 8) ||
+                             (position_.Tile.X == 15 && position_.DeltaPixel.X == 8))) {
+                        if (position_.Tile.Y == 14) {
+                            initialJumps_--;
+                            if (initialJumps_ == 0) {
+                                if (position_.Tile.X == 11) {
+                                    direction_ = Direction.Right;
+                                } else {
+                                    direction_ = Direction.Left;
+                                }
+                            } else {
+                                direction_ = Direction.Up;
+                            }
+                        } else if (position_.Tile.Y == 13) {
+                            direction_ = Direction.Down;
+                        }
+                    }
+                    break;
+                case GhostState.Scatter:
+                    // Attempt to reverse direction upon entering this state
+                    if (previousState_ == GhostState.Attack) {
+                        scatterModesLeft_--;
+                        if (NextTile(OppositeDirection(direction_)).IsOpen) {
+                            direction_ = OppositeDirection(direction_);
+                        }
+                    }
+                    AIScatter();
+                    int timeInScatterMode = scatterModesLeft_ <= 2 ? 5 : 7;
+                    if ((DateTime.Now - timeInCurrentState) > TimeSpan.FromSeconds(timeInScatterMode)) {
+                        State = GhostState.Attack;
+                    }
+                    break;
+                case GhostState.Dead:
+                    // Attempt to reverse direction upon entering this state
+                    if (previousState_ != GhostState.Dead && previousState_ != GhostState.Blue) {
+                        if (NextTile(OppositeDirection(direction_)).IsOpen) {
+                            direction_ = OppositeDirection(direction_);
+                        }
+                    } else {
+                        AIDead();
+                    }
+                    if (position_.DeltaPixel.X == 8 && position_.DeltaPixel.Y == 8) {
+                        if (position_.Tile.Y == 14) {
+                            State = GhostState.Home;
+                        }
+                    }
+                    break;
+                case GhostState.Attack:
+                    // Attempt to reverse direction upon entering this state
+                    if (previousState_ != GhostState.Attack && previousState_ != GhostState.Blue) {
+                        if (NextTile(OppositeDirection(direction_)).IsOpen) {
+                            direction_ = OppositeDirection(direction_);
+                        }
+                    } else {
+                        AIAttack();
+                    }
+
+                    if ((DateTime.Now - timeInCurrentState) > TimeSpan.FromSeconds(20)) {
+                        State = GhostState.Scatter;
+                    }
+                    break;
+                case GhostState.Blue:
+                    // Attempt to reverse direction upon entering this state
+                    if (previousState_ != GhostState.Blue) {
+                        if (NextTile(OppositeDirection(direction_)).IsOpen) {
+                            direction_ = OppositeDirection(direction_);
+                        }
+                    } else {
+                        // TODO : make special blue AI
+                        AIAttack();
+                    }
+
+                    // When blue time is over, revert to attack mode.
+                    if ((DateTime.Now - timeInCurrentState) > TimeSpan.FromSeconds(Constants.BlueTime())) {
+                        State = GhostState.Attack;
+                    }
+                    break;
+            }
+
+            // TODO : move all these magic numbers to the Constants class.
+            // We select a new sound only upon some state change, or when 
+            // the number of crumps goes below certain thresholds.
+            if ((initialState != previousState_) ||
+                (Grid.NumCrumps == 199 && previousNumCrumps_ == 200) ||
+                (Grid.NumCrumps == 19 && previousNumCrumps_ == 20)) {
+                PlaySound();
+            }
+            previousState_ = initialState;
+        }
+
+        void Move() {
+            updateCount_++;
+            updateCount_ %= updatesPerPixel_;
+            if (updateCount_ == 0) {
+                // There is no point running the full AI each update, especially considering we
+                // want this code to run at 1000 updates per second. We run it each time it moves by a pixel.
+                scheduleStateEval_ = true;
+
+                // Now is a nice time to evaluate whether our current speed is correct. This variable
+                // may change under a wide array of circumstances, so by putting it here we probably generate
+                // too many checks but we ensure correct behavior.
+                UpdateSpeed();
+
+                switch (direction_) {
+                    case Direction.Up:
+                        position_.DeltaPixel.Y--;
+                        if (position_.DeltaPixel.Y < 0) {
+                            position_.DeltaPixel.Y = 15;
+                            position_.Tile.Y--;
+                        }
+                        break;
+                    case Direction.Down:
+                        position_.DeltaPixel.Y++;
+                        if (position_.DeltaPixel.Y > 15) {
+                            position_.DeltaPixel.Y = 0;
+                            position_.Tile.Y++;
+                        }
+                        break;
+                    case Direction.Left:
+                        position_.DeltaPixel.X--;
+                        if (position_.DeltaPixel.X < 0) {
+                            position_.DeltaPixel.X = 15;
+                            position_.Tile.X--;
+                            // Special case : the tunnel
+                            if (position_.Tile.X < 0) {
+                                position_.Tile.X = Grid.Width - 1;
+                            }
+                        }
+                        break;
+                    case Direction.Right:
+                        position_.DeltaPixel.X++;
+                        if (position_.DeltaPixel.X > 15) {
+                            position_.DeltaPixel.X = 0;
+                            position_.Tile.X++;
+                            // Special case : the tunnel
+                            if (position_.Tile.X > Grid.Width - 1) {
+                                position_.Tile.X = 0;
+                            }
+                        }
+                        break;
+                }
+            }
+        }
+
+        void UpdateSpeed() {
+            int baseSpeed = Constants.GhostSpeed();
+            if (State == GhostState.Home) {
+                updatesPerPixel_ = baseSpeed * 2;
+            } else if (State == GhostState.Blue) {
+                updatesPerPixel_ = (int)(baseSpeed * 1.5);
+            } else if (identity_ == Ghosts.Blinky && Grid.NumCrumps <= Constants.CruiseElroyTimer()) {
+                updatesPerPixel_ = baseSpeed - 1;
+            }
+                // If in the tunnel, reduce speed
+              else if (position_.Tile.Y == 14 &&
+                  ((0 <= position_.Tile.X && position_.Tile.X <= 5) ||
+                    (22 <= position_.Tile.X && position_.Tile.X <= 27))) {
+                updatesPerPixel_ = baseSpeed + 5;
+            } else {
+                updatesPerPixel_ = baseSpeed;
+            }
+
+        }
+
+        /// <summary>
+        /// Guides the ghost towards his "favored" area of the board, as defined by scatterTiles_.
+        /// </summary> 
+        void AIScatter() {
+            // As with AIAttack(), all the method does is change direction if necessary,
+            // which only happens when exactly at a junction
+            if (position_.DeltaPixel != Point.Zero || !IsAJunction(position_.Tile)) {
+                return;
+            }
+
+            // Scatter AI may be overriden by certain tiles
+            if (AIOverride()) {
+                return;
+            }
+
+            // If we are on one of our favored tiles, go on to the next
+            int favoredTile = scatterTiles_.FindIndex(i => i == position_.Tile);
+            int nextFavoredTile = (favoredTile + 1) % (scatterTiles_.Count);
+            if (favoredTile != -1) {
+                direction_ = FindDirection(scatterTiles_[nextFavoredTile]);
+            }
+                // If our last junction was a favored tile and this one isn't, ignore it
+            else if (!scatterTiles_.Contains(lastJunction_)) {
+                // Otherwise, find scatter tile closest to our position and head for it.
+                List<Point> orderedTiles = scatterTiles_.ToList();
+                orderedTiles.Sort((a, b) => Vector2.Distance(new Vector2(position_.Tile.X, position_.Tile.Y),
+                                                             new Vector2(a.X, a.Y)).
+                                            CompareTo(
+                                            Vector2.Distance(new Vector2(position_.Tile.X, position_.Tile.Y),
+                                                             new Vector2(b.X, b.Y))));
+                direction_ = FindDirection(orderedTiles[0]);
+            }
+        }
+
+        /// <summary>
+        /// Guides the ghost to try to reach the player
+        /// </summary>
+        void AIAttack() {
+            // All this method does is change direction if necessary.
+            // There is only one case in which we may potentially change direction :
+            // when the ghost is exactly at a junction.
+            if (position_.DeltaPixel != Point.Zero || !IsAJunction(position_.Tile)) {
+                return;
+            }
+
+            // Attack AI may be overriden by certain tiles
+            if (AIOverride()) {
+                return;
+            }
+
+            switch (identity_) {
+                case Ghosts.Blinky:
+                    AttackAIBlinky();
+                    break;
+                case Ghosts.Pinky:
+                    AttackAIPinky();
+                    break;
+                case Ghosts.Inky:
+                    AttackAIInky();
+                    break;
+                case Ghosts.Clyde:
+                    AttackAIClyde();
+                    break;
+                default:
+                    throw new ArgumentException();
+            }
+        }
+
+        void AIDead() {
+
+            // When aligned with the entrance, go down. When the ghost has fully entered the house,
+            // stateEval will change state to Home.
+            if (position_.Tile.Y == 11 && position_.Tile.X == 13 && position_.DeltaPixel.X == 8) {
+                direction_ = Direction.Down;
+            }
+                // Otherwise, only change direction if the ghost is exactly on a tile
+            else if (position_.DeltaPixel == Point.Zero) {
+                // We direct the ghost towards one of the two squares above the home. When he reaches one,
+                // he still has to move slightly right or left in order to align with the entrance.
+                if (position_.Tile.X == 13 && position_.Tile.Y == 11) {
+                    direction_ = Direction.Right;
+                } else if (position_.Tile.X == 14 && position_.Tile.Y == 11) {
+                    direction_ = Direction.Left;
+                }
+                    // Otherwise, when at a junction, head for the square above home closest to us.
+                else if (IsAJunction(position_.Tile)) {
+                    if (position_.Tile.X > 13) {
+                        direction_ = FindDirection(new Point(14, 11));
+                    } else {
+                        direction_ = FindDirection(new Point(13, 11));
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// Blinky is extremely straightforward : head directly for the player
+        /// </summary>
+        void AttackAIBlinky() {
+            direction_ = FindDirection(player_.Position.Tile);
+        }
+
+        /// <summary>
+        /// Pinky tries to head for two tiles ahead of the player.
+        /// </summary>
+        void AttackAIPinky() {
+            Tile nextTile = NextTile(player_.Direction, player_.Position);
+            Tile nextNextTile = NextTile(player_.Direction, new Position(nextTile.ToPoint, Point.Zero));
+            direction_ = FindDirection(nextNextTile.ToPoint);
+        }
+
+        /// <summary>
+        /// Inky is a bit more random. He will try to head for a square situated across
+        /// the pac man from blinky's location.
+        /// </summary>
+        void AttackAIInky() {
+            Tile nextTile = NextTile(player_.Direction, player_.Position);
+            Tile nextNextTile = NextTile(player_.Direction, new Position(nextTile.ToPoint, Point.Zero));
+            Vector2 line = new Vector2(blinky_.Position.Tile.X - nextNextTile.ToPoint.X, blinky_.Position.Tile.Y - nextNextTile.ToPoint.Y);
+            line *= 2;
+            Point destination = new Point(position_.Tile.X + (int)line.X, position_.Tile.Y + (int)line.Y);
+            // Prevent out-of-bounds exception
+            destination.X = (int)MathHelper.Clamp(destination.X, 0, Grid.Width - 1);
+            destination.Y = (int)MathHelper.Clamp(destination.Y, 0, Grid.Height - 1);
+            direction_ = FindDirection(destination);
+        }
+
+        /// <summary>
+        /// Clyde is the bizarre one. When within 8 tiles of the player,
+        /// he will head for his favored corner (AIScatter). When farther away,
+        /// he will near the player using Blinky's AI.
+        /// </summary>
+        void AttackAIClyde() {
+            float distanceToPlayer = Vector2.Distance(
+                new Vector2(player_.Position.Tile.X, player_.Position.Tile.Y),
+                new Vector2(position_.Tile.X, position_.Tile.Y));
+            if (distanceToPlayer >= 8) {
+                AttackAIBlinky();
+            } else {
+                AIScatter();
+            }
+        }
+
+        // On certain tiles, we force all the ghosts in a particular direction.
+        // This is to prevent them from bunching up too much, and give attentive
+        // players some sure-fire ways to escape.
+        bool AIOverride() {
+            if (position_.Tile.Y == 11 && (position_.Tile.X == 12 || position_.Tile.X == 15)) {
+                return (direction_ == Direction.Right || direction_ == Direction.Left);
+            } else if (position_.Tile.Y == 20 && (position_.Tile.X == 9 || position_.Tile.X == 18)) {
+                return (direction_ == Direction.Right || direction_ == Direction.Left);
+            } else {
+                return false;
+            }
+        }
+
+
+        /// <summary>
+        /// Returns the opposite of the specified direction 
+        /// </summary>
+        /// <param name="d">direction</param>
+        /// <returns>opposite direction</returns>
+        Direction OppositeDirection(Direction d) {
+            switch (d) {
+                case Direction.Up:
+                    return Direction.Down;
+                case Direction.Down:
+                    return Direction.Up;
+                case Direction.Left:
+                    return Direction.Right;
+                case Direction.Right:
+                    return Direction.Left;
+                default:
+                    throw new ArgumentException();
+            }
+        }
+
+        /// <summary>
+        /// Returns in what direction we should go next in order to reach the destination.
+        /// </summary>
+        /// <param name="destination">Where we want to go</param>
+        /// <returns>Where to go next</returns>
+        Direction FindDirection(Point destination) {
+            // We use a stupid but very efficient algorithm : go in the direction that
+            // closes most distance between position and destination. This works well given
+            // there are no dead ends and multiple paths leading to the destination.
+            // However, we can never turn 180, and will rather choose a less-than-optimal path.
+            int xDistance = destination.X - position_.Tile.X;
+            int yDistance = destination.Y - position_.Tile.Y;
+
+            var directions = new List<Direction>(4);
+
+            // Order directions by shortest path. Note: there is probably a better way to do this, probably if I hadn't used an
+            // enum for directions in the first place. 
+            if (Math.Abs(xDistance) > Math.Abs(yDistance)) {
+                if (xDistance > 0) {
+                    if (yDistance < 0) { // Direction is Up-Right, favor Right
+                        directions = new List<Direction> { Direction.Right, Direction.Up, Direction.Down, Direction.Left };
+                    } else { // Direction is Down-Right, favor Right
+                        directions = new List<Direction> { Direction.Right, Direction.Down, Direction.Up, Direction.Left };
+                    }
+                } else {
+                    if (yDistance < 0) { // Direction is Up-Left, favor Left
+                        directions = new List<Direction> { Direction.Left, Direction.Up, Direction.Down, Direction.Right };
+                    } else { // Direction is Down-Left, favor Left
+                        directions = new List<Direction> { Direction.Left, Direction.Down, Direction.Up, Direction.Right };
+                    }
+                }
+            } else {
+                if (xDistance > 0) {
+                    if (yDistance < 0) { // Direction is Up-Right, favor Up
+                        directions = new List<Direction> { Direction.Up, Direction.Right, Direction.Left, Direction.Down };
+                    } else { // Direction is Down-Right, favor Down
+                        directions = new List<Direction> { Direction.Down, Direction.Right, Direction.Left, Direction.Up };
+                    }
+                } else {
+                    if (yDistance < 0) { // Direction is Up-Left, favor Up
+                        directions = new List<Direction> { Direction.Up, Direction.Left, Direction.Right, Direction.Down };
+                    } else { // Direction is Down-Left, favor Down
+                        directions = new List<Direction> { Direction.Down, Direction.Left, Direction.Right, Direction.Up };
+                    }
+                }
+            }
+
+            // Select first item in the list to meet two essential conditions : the path ahead is open,
+            // and it's not the opposite direction. If we can't meet both conditions, just return the first
+            // direction that leads to an open square. 
+            int index = directions.FindIndex(i => i != OppositeDirection(direction_) && NextTile(i).IsOpen);
+            if (index != -1) {
+                return directions[index];
+            } else {
+                // Put a breakpoint here, this should never happen.
+                return directions.Find(i => NextTile(i).IsOpen);
+            }
+        }
+
+
+        /// <summary>
+        /// Retrieves the next tile in the specified direction from the ghost's position.
+        /// </summary>
+        /// <param name="d">Direction in which to look</param>
+        /// <returns>The tile</returns>
+        Tile NextTile(Direction d) {
+            return NextTile(d, position_);
+        }
+
+        /// <summary>
+        /// Retrieves the next tile in the specified direction from the specified position.
+        /// </summary>
+        /// <param name="d">Direction in which to look</param>
+        /// <param name="p">Position from which to look</param>
+        /// <returns>The tile</returns>
+        public static Tile NextTile(Direction d, Position p) {
+            switch (d) {
+                case Direction.Up:
+                    if (p.Tile.Y - 1 < 0) {
+                        return Grid.TileGrid[p.Tile.X, p.Tile.Y];
+                    } else {
+                        return Grid.TileGrid[p.Tile.X, p.Tile.Y - 1];
+                    }
+                case Direction.Down:
+                    if (p.Tile.Y + 1 >= Grid.Height) {
+                        return Grid.TileGrid[p.Tile.X, p.Tile.Y];
+                    } else {
+                        return Grid.TileGrid[p.Tile.X, p.Tile.Y + 1];
+                    }
+                case Direction.Left:
+                    // Special case : the tunnel
+                    if (p.Tile.X == 0) {
+                        return Grid.TileGrid[Grid.Width - 1, p.Tile.Y];
+                    } else {
+                        return Grid.TileGrid[p.Tile.X - 1, p.Tile.Y];
+                    }
+                case Direction.Right:
+                    // Special case : the tunnel
+                    if (p.Tile.X + 1 >= Grid.Width) {
+                        return Grid.TileGrid[0, p.Tile.Y];
+                    } else {
+                        return Grid.TileGrid[p.Tile.X + 1, p.Tile.Y];
+                    }
+                default:
+                    throw new ArgumentException();
+            }
+        }
+
+        /// <summary>
+        /// Returns whether the specified tile is a junction.
+        /// </summary>
+        /// <param name="tile">Tile to check</param>
+        /// <returns>whether the specified tile is a junction</returns>
+        bool IsAJunction(Point tile) {
+            if (NextTile(direction_).Type == TileTypes.Open) {
+                // If the path ahead is open, we're at a junction if it's also open
+                // to one of our sides
+                if (direction_ == Direction.Up || direction_ == Direction.Down) {
+                    return ((NextTile(Direction.Left).IsOpen) ||
+                            (NextTile(Direction.Right).IsOpen));
+                } else {
+                    return ((NextTile(Direction.Up).IsOpen) ||
+                            (NextTile(Direction.Down).IsOpen));
+                }
+            }
+                // If the path ahead is blocked, then we're definitely at a junction, because there are no dead-ends
+            else {
+                return true;
+            }
+        }
+
+        /// <summary>
+        /// Assumes spritebatch.begin() was called
+        /// </summary>
+        /// <param name="gameTime">Provides a snapshot of timing values.</param>
+        public void Draw(GameTime gameTime, Vector2 boardPosition) {
+            // Ghosts have a two-frame animation; we use a bool to toggle between
+            // the two. We divide DateTime.Now.Milliseconds by the period.
+            if (((DateTime.Now.Millisecond / 125) % 2) == 0 ^ wiggle_) {
+                wiggle_ = !wiggle_;
+            }
+            Vector2 position;
+            position.X = boardPosition.X + (position_.Tile.X * 16) + (position_.DeltaPixel.X) - 6;
+            position.Y = boardPosition.Y + (position_.Tile.Y * 16) + (position_.DeltaPixel.Y) - 6;
+            Vector2 eyesBasePosition;
+            eyesBasePosition.X = position.X + 4;
+            eyesBasePosition.Y = position.Y + 6;
+            Vector2 eyesCenterPosition = new Vector2();
+            switch (direction_) {
+                case Direction.Up:
+                    eyesBasePosition.Y -= 2;
+                    eyesCenterPosition.X = eyesBasePosition.X + 2;
+                    eyesCenterPosition.Y = eyesBasePosition.Y;
+                    break;
+                case Direction.Down:
+                    eyesBasePosition.Y += 2;
+                    eyesCenterPosition.X = eyesBasePosition.X + 2;
+                    eyesCenterPosition.Y = eyesBasePosition.Y + 6;
+                    break;
+                case Direction.Left:
+                    eyesBasePosition.X -= 2;
+                    eyesCenterPosition.X = eyesBasePosition.X;
+                    eyesCenterPosition.Y = eyesBasePosition.Y + 3;
+                    break;
+                case Direction.Right:
+                    eyesBasePosition.X += 2;
+                    eyesCenterPosition.X = eyesBasePosition.X + 4;
+                    eyesCenterPosition.Y = eyesBasePosition.Y + 3;
+                    break;
+            }
+            if (State == GhostState.Blue) {
+                if (((DateTime.Now - timeInCurrentState).Seconds < 0.5 * Constants.BlueTime())) {
+                    RenderSprite(wiggle_ ? ghostBase1_ : ghostBase2_, null, boardPosition, position, Color.Blue);
+                    RenderSprite(ghostChased_, null, boardPosition, position, Color.White);
+                } else {
+                    bool flash = (DateTime.Now.Second + DateTime.Now.Millisecond / 200) % 2 == 0;
+                    RenderSprite(wiggle_ ? ghostBase1_ : ghostBase2_, null, boardPosition, position, flash ? Color.Blue : Color.White);
+                    RenderSprite(ghostChased_, null, boardPosition, position, flash ? Color.White : Color.Blue);
+                }
+            } else if (State == GhostState.Dead) {
+                RenderSprite(eyesBase_, null, boardPosition, eyesBasePosition, Color.White);
+                RenderSprite(eyesCenter_, null, boardPosition, eyesCenterPosition, Color.White);
+            } else {
+                RenderSprite(wiggle_ ? ghostBase1_ : ghostBase2_, null, boardPosition, position, colorBase_);
+                RenderSprite(eyesBase_, null, boardPosition, eyesBasePosition, Color.White);
+                RenderSprite(eyesCenter_, null, boardPosition, eyesCenterPosition, Color.White);
+            }
+
+        }
+
+        /// <summary>
+        /// Allows rendering across the tunnel, which is tricky.
+        /// </summary>
+        /// <param name="spriteSheet">Source texture</param>
+        /// <param name="rectangle">Portion of the source to render. Pass null for rendering the whole texture.</param>
+        /// <param name="boardPosition">Top-left pixel of the board in the screen</param>
+        /// <param name="position">Where to render the texture.</param>
+        void RenderSprite(Texture2D spriteSheet, Rectangle? rectangle, Vector2 boardPosition, Vector2 position, Color color) {
+
+            Rectangle rect = rectangle == null ? new Rectangle(0, 0, spriteSheet.Width, spriteSheet.Height) :
+                                                rectangle.Value;
+            int textureWidth = rectangle == null ? spriteSheet.Width : rectangle.Value.Width;
+            int textureHeight = rectangle == null ? spriteSheet.Height : rectangle.Value.Height;
+
+            // What happens when we are crossing to the other end by the tunnel?
+            // We detect if part of the sprite is rendered outside of the board.
+            // First, to the left.
+            if (position.X < boardPosition.X) {
+                int deltaPixel = (int)(boardPosition.X - position.X); // Number of pixels off the board
+                var leftPortion = new Rectangle(rect.X + deltaPixel, rect.Y, textureWidth - deltaPixel, textureHeight);
+                var leftPortionPosition = new Vector2(boardPosition.X, position.Y);
+                var rightPortion = new Rectangle(rect.X, rect.Y, deltaPixel, textureHeight);
+                var rightPortionPosition = new Vector2(boardPosition.X + (16 * 28) - deltaPixel, position.Y);
+                spriteBatch_.Draw(spriteSheet, leftPortionPosition, leftPortion, color);
+                spriteBatch_.Draw(spriteSheet, rightPortionPosition, rightPortion, color);
+            }
+                // Next, to the right
+            else if (position.X > (boardPosition.X + (16 * 28) - textureWidth)) {
+                int deltaPixel = (int)((position.X + textureWidth) - (boardPosition.X + (16 * 28))); // Number of pixels off the board
+                var leftPortion = new Rectangle(rect.X + textureWidth - deltaPixel, rect.Y, deltaPixel, textureHeight);
+                var leftPortionPosition = new Vector2(boardPosition.X, position.Y);
+                var rightPortion = new Rectangle(rect.X, rect.Y, textureWidth - deltaPixel, textureHeight);
+                var rightPortionPosition = new Vector2(position.X, position.Y);
+                spriteBatch_.Draw(spriteSheet, leftPortionPosition, leftPortion, color);
+                spriteBatch_.Draw(spriteSheet, rightPortionPosition, rightPortion, color);
+            }
+                // Draw normally - in one piece
+            else {
+                spriteBatch_.Draw(spriteSheet, position, rect, color);
+            }
+        }
+
+
+
+        // DRAWING
+        SpriteBatch spriteBatch_;
+        Texture2D ghostBase1_;
+        Texture2D ghostBase2_;
+        Texture2D ghostChased_;
+        Texture2D eyesBase_;
+        Texture2D eyesCenter_;
+        Color colorBase_;
+        bool wiggle_;
+
+        // LOGIC
+        Ghost blinky_; // Only Inky needs this for his AI
+        Ghosts identity_;
+        Direction direction_;
+        Position position_;
+        GhostState state_;
+        GhostState previousState_;
+        List<Point> scatterTiles_;
+        Point lastJunction_;
+        DateTime timeInCurrentState;
+        Player player_;
+        int updatesPerPixel_;
+        bool scheduleStateEval_;
+        int scatterModesLeft_;
+        int initialJumps_;
+        int previousNumCrumps_;
+        int updateCount_;
+
+        public GhostState State { get { return state_; } set { state_ = value; timeInCurrentState = DateTime.Now; } }
+        public Position Position { get { return position_; } }
+        public Ghosts Identity { get { return identity_; } }
+    }
+}

+ 86 - 0
Samples/MacOS/XNAPacMan/GhostSoundsManager.cs

@@ -0,0 +1,86 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.Xna.Framework.Audio;
+using Microsoft.Xna.Framework;
+
+namespace XNAPacMan {
+    /// <summary>
+    /// All four ghosts use the same sounds, and only one can be played at a time. So, instead of having to
+    /// synchronize each other, they use this class.
+    /// </summary>
+    static class GhostSoundsManager {
+
+        static public void Init(Game game) {
+            soundBank_ = (SoundBank)game.Services.GetService(typeof(SoundBank));
+            InitCues();
+        }
+
+        static public void playLoopAttack() {
+            playLoop(ref loopAttack_);
+        }
+
+        static public void playLoopAttackFast() {
+            playLoop(ref loopAttackFast_);
+        }
+
+        static public void playLoopAttackVeryFast() {
+            playLoop(ref loopAttackVeryFast_);
+        }
+
+        static public void playLoopBlue() {
+            playLoop(ref loopBlue_);
+        }
+
+        static public void playLoopDead() {
+            playLoop(ref loopDead_);
+        }
+
+
+        static void playLoop(ref Cue cue) {
+            if (!cue.IsPlaying) {
+                StopLoops();
+                InitCues();
+                cue.Play();
+            }
+        }
+
+        static void InitCues() {
+            loopAttack_ = soundBank_.GetCue("GhostNormalLoop1");
+            loopAttackFast_ = soundBank_.GetCue("GhostFastLoop");
+            loopAttackVeryFast_ = soundBank_.GetCue("GhostVFastLoop");
+            loopDead_ = soundBank_.GetCue("GhostRunningHome");
+            loopBlue_ = soundBank_.GetCue("GhostChased");
+        }
+
+        static public void StopLoops() {
+            loopAttack_.Stop(AudioStopOptions.AsAuthored);
+            loopAttackFast_.Stop(AudioStopOptions.AsAuthored);
+            loopAttackVeryFast_.Stop(AudioStopOptions.AsAuthored);
+            loopDead_.Stop(AudioStopOptions.AsAuthored);
+            loopBlue_.Stop(AudioStopOptions.AsAuthored);
+        }
+
+        static public void PauseLoops() {
+            loopAttack_.Pause();
+            loopAttackFast_.Pause();
+            loopAttackVeryFast_.Pause();
+            loopDead_.Pause();
+            loopBlue_.Pause();
+        }
+
+        static public void ResumeLoops() {
+            loopAttack_.Resume();
+            loopAttackFast_.Resume();
+            loopAttackVeryFast_.Resume();
+            loopDead_.Resume();
+            loopBlue_.Resume();
+        }
+
+        static SoundBank soundBank_;
+        static Cue loopAttack_;
+        static Cue loopAttackFast_;
+        static Cue loopAttackVeryFast_;
+        static Cue loopBlue_;
+        static Cue loopDead_;
+    }
+}

+ 184 - 0
Samples/MacOS/XNAPacMan/Grid.cs

@@ -0,0 +1,184 @@
+using System;
+using System.IO;
+using System.Text;
+using Microsoft.Xna.Framework;
+
+namespace XNAPacMan {
+
+
+    /// <summary>
+    /// By who the tile can be traversed
+    /// </summary>
+    public enum TileTypes {
+        /// <summary>
+        /// Everyone can go through
+        /// </summary>
+        Open,
+        /// <summary>
+        /// No one can go through
+        /// </summary>
+        Closed,
+        /// <summary>
+        /// Under special circumstances ghosts can go there
+        /// </summary>
+        Home
+    }
+
+    /// <summary>
+    /// Represents the maze in terms of tiles. Initializes itself from txt file.
+    /// </summary>
+    public static class Grid {
+
+        /// <summary>
+        /// Creates a new Grid object
+        /// </summary>
+        static Grid() {
+            initializeFromFile();
+        }
+
+
+        /// <summary>
+        /// Reads Grid.txt to get an object grid from the numbers.
+        /// </summary>
+        static void initializeFromFile() {
+            TextReader tr = new StreamReader("Content/Grid.txt");
+            string line = tr.ReadLine();
+            int lineIndex = 0;
+            int charIndex = 0;
+
+            while (line != null) {
+                foreach (char c in line) {
+                    if (c == '1') {
+                        TileGrid[charIndex, lineIndex] = new Tile(TileTypes.Open, true, false, new Point(charIndex, lineIndex));
+                    }
+                    else if (c == '0') {
+                        TileGrid[charIndex, lineIndex]= new Tile(TileTypes.Closed, false, false, new Point(charIndex, lineIndex));
+                    }
+                    else if (c == '2') {
+                        TileGrid[charIndex, lineIndex] = new Tile(TileTypes.Home, false, false, new Point(charIndex, lineIndex));
+                    }
+                    else if (c == '3') {
+                        TileGrid[charIndex, lineIndex]= new Tile(TileTypes.Open, true, true, new Point(charIndex, lineIndex));
+                    }
+                    if (c != ' ') {
+                        charIndex++;
+                    }
+                }
+                charIndex = 0;
+                lineIndex++;
+                line = tr.ReadLine();
+            }
+
+            tr.Close();
+			
+			// Now, actually a few open tiles do not contain a crump; such as 
+			// the tunnels and around the ghosts' home. 
+			for (int i = 0; i < 28; i++) {
+				if (i != 6 && i != 21) {
+					TileGrid[i, 14].HasCrump = false;
+				}
+			}
+			
+			for (int j = 11; j < 20; j++) {
+				TileGrid[9, j].HasCrump = false;
+				TileGrid[18, j].HasCrump = false;
+			}
+			
+			for (int i = 10; i < 18; i++) {
+				TileGrid[i, 11].HasCrump = false;
+				TileGrid[i, 17].HasCrump = false;
+			}
+			
+			TileGrid[12, 9].HasCrump = false;
+			TileGrid[15, 9].HasCrump = false;
+			TileGrid[12, 10].HasCrump = false;
+			TileGrid[15, 10].HasCrump = false;
+            TileGrid[13, 23].HasCrump = false;
+            TileGrid[14, 23].HasCrump = false;
+        }
+
+        
+
+        static Tile[,] tileGrid_ = new Tile[28, 31];
+
+        public static Tile[,] TileGrid {
+            get { return tileGrid_; }
+        }
+
+        public static int Width {
+            get { return 28; }
+        }
+        public static int Height {
+            get { return 31; }
+        }
+        public static int NumCrumps { get; set; }
+
+        public static void Reset() {
+            NumCrumps = 0;
+            initializeFromFile();
+        }
+    }
+
+    /// <summary>
+    /// A square of the maze
+    /// </summary>
+    public struct Tile {
+
+        TileTypes type_;
+        /// <summary>
+        /// The type of the tile
+        /// </summary>
+        public TileTypes Type {
+            get { return type_; }
+            set { type_ = value; }
+        }
+
+        bool hasCrump_;
+        /// <summary>
+        /// Whether the tile has a crump
+        /// </summary>
+        public bool HasCrump {
+            get { return hasCrump_; }
+            set {
+                if (value != hasCrump_) {
+                    Grid.NumCrumps += value ? 1 : -1;
+                } 
+                hasCrump_ = value;
+            }
+        }
+
+        bool hasPowerPill_;
+        /// <summary>
+        /// Whether the tile has a power pill
+        /// </summary>
+        public bool HasPowerPill {
+            get { return hasPowerPill_; }
+            set { hasPowerPill_ = value; }
+        }
+
+        public bool IsOpen {
+            get { return type_ == TileTypes.Open; }
+        }
+
+        Point position_;
+        public Point ToPoint { get { return position_; } }
+
+        /// <summary>
+        /// Sets the different attributes
+        /// </summary>
+        /// <param name="type">The type of tile</param>
+        /// <param name="hasCrump">Whether the tile has a crump</param>
+        /// <param name="hasPowerPill">Whether the tile has a power pill</param>
+        public Tile(TileTypes type, bool hasCrump, bool hasPowerPill, Point position) {
+            type_ = type;
+            hasCrump_ = hasCrump;
+            if (hasCrump) {
+                Grid.NumCrumps++;
+            }
+            hasPowerPill_ = hasPowerPill;
+            position_ = position;
+        }
+    }
+
+
+}

+ 90 - 0
Samples/MacOS/XNAPacMan/HighScores.cs

@@ -0,0 +1,90 @@
+using System;
+using System.IO;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Input;
+using Microsoft.Xna.Framework.Audio;
+using Microsoft.Xna.Framework.Graphics;
+
+
+namespace XNAPacMan {
+    /// <summary>
+    /// This is a game component that implements IUpdateable.
+    /// </summary>
+    public class HighScores : Microsoft.Xna.Framework.DrawableGameComponent {
+        public HighScores(Game game)
+            : base(game) {
+            // TODO: Construct any child components here
+        }
+
+        /// <summary>
+        /// Allows the game component to perform any initialization it needs to before starting
+        /// to run.  This is where it can query for any required services and load content.
+        /// </summary>
+        public override void Initialize() {
+            scores_ = new List<string>(10);
+            const string fileName = "highscores.txt";
+            if (File.Exists(fileName)) {
+                scores_ = File.ReadAllLines(fileName).ToList<string>();
+                scores_.Sort((a, b) => Convert.ToInt32(a).CompareTo(Convert.ToInt32(b)));
+                scores_.Reverse();
+            }
+            scoreFont_ = Game.Content.Load<SpriteFont>("Score");
+            itemFont_ = Game.Content.Load<SpriteFont>("MenuItem");
+            selectionArrow_ = Game.Content.Load<Texture2D>("sprites/Selection");
+            spriteBatch_ = (SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));
+            graphics_ = (GraphicsDeviceManager)Game.Services.GetService(typeof(GraphicsDeviceManager));
+            oldState_ = Keyboard.GetState();
+            base.Initialize();
+        }
+
+        /// <summary>
+        /// Allows the game component to update itself.
+        /// </summary>
+        /// <param name="gameTime">Provides a snapshot of timing values.</param>
+        public override void Update(GameTime gameTime) {
+            // TODO: Add your update code here
+            if (Keyboard.GetState().GetPressedKeys().Length > 0 && oldState_.GetPressedKeys().Length == 0) {
+                Game.Components.Remove(this);
+                Game.Components.Add(new Menu(Game, null));
+            }
+            oldState_ = Keyboard.GetState();
+            base.Update(gameTime);
+        }
+
+        /// <summary>
+        /// Allows the component to draw itself
+        /// </summary>
+        /// <param name="gameTime">Provides a snapshot of timing values</param>
+        public override void Draw(GameTime gameTime) {
+            base.Draw(gameTime);
+            Vector2 position = new Vector2(graphics_.PreferredBackBufferWidth / 2 - 150, graphics_.PreferredBackBufferHeight / 2 - 200);
+            spriteBatch_.Begin();
+            for (int i = 0; i < 10; i++) {
+                spriteBatch_.DrawString(scoreFont_, (i + 1).ToString() + ".", new Vector2(position.X, position.Y + (30 * i)), Color.White);
+                if (i < scores_.Count) {
+                    spriteBatch_.DrawString(scoreFont_, scores_[i], new Vector2(position.X + 50, position.Y + (30 * i)), Color.White);
+                }
+            }
+
+            Vector2 itemPosition;
+            itemPosition.X = (graphics_.PreferredBackBufferWidth / 2) - 100;
+            itemPosition.Y = (graphics_.PreferredBackBufferHeight / 2) + 200;
+            spriteBatch_.Draw(selectionArrow_, new Vector2(itemPosition.X - 50, itemPosition.Y), Color.White);
+            spriteBatch_.DrawString(itemFont_, "Return", itemPosition, Color.Yellow);
+
+            spriteBatch_.End();
+            
+
+        }
+
+        List<string> scores_;
+        SpriteFont scoreFont_;
+        SpriteFont itemFont_;
+        Texture2D selectionArrow_;
+        SpriteBatch spriteBatch_;
+        GraphicsDeviceManager graphics_;
+        KeyboardState oldState_;
+    }
+}

+ 16 - 0
Samples/MacOS/XNAPacMan/Info.plist

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleIdentifier</key>
+	<string>com.yourcompany.XNAPacMan</string>
+	<key>CFBundleName</key>
+	<string>XNAPacMan</string>
+	<key>CFBundleVersion</key>
+	<string>1</string>
+	<key>LSMinimumSystemVersion</key>
+	<string>10.6</string>
+	<key>NSPrincipalClass</key>
+	<string>NSApplication</string>
+</dict>
+</plist>

+ 181 - 0
Samples/MacOS/XNAPacMan/Menu.cs

@@ -0,0 +1,181 @@
+using System;
+using System.IO;
+using System.Linq;
+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.Media;
+using Microsoft.Xna.Framework.Net;
+using Microsoft.Xna.Framework.Storage;
+
+
+namespace XNAPacMan {
+    /// <summary>
+    /// This is a game component that implements IUpdateable.
+    /// </summary>
+    /// Optionally takes a GameLoop argument, when the menu must be able to
+    /// resume the current GameLoop. Otherwise, the reference would be lost
+    /// and the gameLoop garbage collected.
+    public class Menu : Microsoft.Xna.Framework.DrawableGameComponent {
+        public Menu(Game game, GameLoop gameLoop)
+            : base(game) {
+            gameLoop_ = gameLoop;
+            gameStart_ = (gameLoop == null);
+        }
+
+        /// <summary>
+        /// Allows the game component to perform any initialization it needs to before starting
+        /// to run.  This is where it can query for any required services and load content.
+        /// </summary>
+        public override void Initialize() {
+            spriteBatch_ = (SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));
+            graphics_ = (GraphicsDeviceManager)Game.Services.GetService(typeof(GraphicsDeviceManager));
+            soundBank_ = (SoundBank)Game.Services.GetService(typeof(SoundBank));
+            selection_ = 0;
+            if (gameLoop_ == null) {
+                items_ = new string[] { "New Game", "High Scores", "Quit" };
+            }
+            else {
+                items_ = new string[] { "Resume", "Quit Game" };
+            }
+            menuItem_ = Game.Content.Load<SpriteFont>("MenuItem");
+            title_ = Game.Content.Load<Texture2D>("sprites/Title");
+            selectionArrow_ = Game.Content.Load<Texture2D>("sprites/Selection");
+            oldState_ = Keyboard.GetState();
+            base.Initialize();
+        }
+
+        /// <summary>
+        /// Allows the game component to update itself.
+        /// </summary>
+        /// <param name="gameTime">Provides a snapshot of timing values.</param>
+        public override void Update(GameTime gameTime) {
+            base.Update(gameTime);
+            // Wonder why we test for this condition? Just replace gameStart_ by true and
+            // try running the game. The answer should be instantaneous.
+            if (gameStart_) {
+                soundBank_.PlayCue("NewLevel");
+                gameStart_ = false;
+            }
+            
+            KeyboardState newState = Keyboard.GetState();
+            
+            // Get keys pressed now that weren't pressed before
+            var newPressedKeys = from k in newState.GetPressedKeys()
+                                 where !(oldState_.GetPressedKeys().Contains(k))
+                                 select k;             
+
+            // Scroll through menu items
+            if (newPressedKeys.Contains(Keys.Down)) {
+                selection_++;
+                selection_ %= items_.Length;
+                soundBank_.PlayCue("PacMAnEat1");
+            }
+            else if (newPressedKeys.Contains(Keys.Up)) {
+                selection_--;
+                selection_ = (selection_ < 0? items_.Length -1 : selection_);
+                soundBank_.PlayCue("PacManEat2");
+            }
+            else if (newPressedKeys.Contains(Keys.Enter)) {
+                menuAction();
+            }
+
+            // Update keyboard state for next update
+            oldState_ = newState;
+
+            
+        }
+
+        /// <summary>
+        /// This is called when the game should draw itself.
+        /// </summary>
+        /// <param name="gameTime">Provides a snapshot of timing values.</param>
+        public override void Draw(GameTime gameTime) {
+            base.Draw(gameTime);
+
+            // The menu is a main component, so it is responsible for initializing the sprite batch each frame
+            spriteBatch_.Begin();
+
+            // Draw title
+            spriteBatch_.Draw(title_, new Vector2((graphics_.PreferredBackBufferWidth / 2) - (title_.Width / 2), 75), Color.White);
+
+            // Draw items
+            Vector2 itemPosition;
+            itemPosition.X = (graphics_.PreferredBackBufferWidth / 2) - 100;
+            for (int i = 0; i < items_.Length; i++) {
+
+                itemPosition.Y = (graphics_.PreferredBackBufferHeight / 2) - 60 + (60 * i);
+                if (i == selection_) {
+                    spriteBatch_.Draw(selectionArrow_, new Vector2(itemPosition.X - 50, itemPosition.Y), Color.White);
+                }
+                spriteBatch_.DrawString(menuItem_, items_[i], itemPosition, Color.Yellow);
+            }
+
+            spriteBatch_.End();
+        }
+
+        void menuAction() {
+            Game.Components.Remove(this);
+            switch (items_[selection_]) {
+                case ("Resume"):
+                    Game.Components.Add(gameLoop_);
+                    break;
+                case ("New Game") :
+                    Game.Components.Add(new GameLoop(Game));
+                    break;
+                case ("High Scores"):
+                    Game.Components.Add(new HighScores(Game));
+                    break;
+                case ("Quit"):
+                    Game.Exit();
+                    break;
+                case ("Quit Game"):
+                    Game.Components.Add(new Menu(Game, null));
+                    SaveHighScore(gameLoop_.Score);
+                    break;
+                default:
+                    throw new ArgumentException("\"" + items_[selection_] + "\" is not a valid case");
+
+            }
+        }
+
+        /// <summary>
+        /// Keep a history of the best 10 scores
+        /// </summary>
+        /// <param name="highScore">New score to save, might make it inside the list, might not.</param>
+        public static void SaveHighScore(int highScore) {
+            const string fileName = "highscores.txt";
+            if (!File.Exists(fileName)) {
+                File.WriteAllLines(fileName, new string[] { highScore.ToString() });
+            }
+            else {
+                List<string> contents = File.ReadAllLines(fileName).ToList<string>();
+                contents.Add(highScore.ToString());
+                if (contents.Count >= 10) {
+                    contents.Sort((a, b) => Convert.ToInt32(a).CompareTo(Convert.ToInt32(b)));
+                    while (contents.Count > 10) {
+                        contents.RemoveAt(0);
+                    }
+                }
+                File.WriteAllLines(fileName, contents.ToArray());
+            }
+        }
+
+        GameLoop gameLoop_;
+        SoundBank soundBank_;
+        GraphicsDeviceManager graphics_;
+        SpriteBatch spriteBatch_;
+        SpriteFont menuItem_;
+        string[] items_;
+        int selection_;
+        bool gameStart_;
+        Texture2D title_;
+        Texture2D selectionArrow_;
+
+        KeyboardState oldState_;
+    }
+}

+ 371 - 0
Samples/MacOS/XNAPacMan/Player.cs

@@ -0,0 +1,371 @@
+using System;
+using System.Linq;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+
+
+namespace XNAPacMan {
+    /// <summary>
+    /// Defines the position of an entity (player, ghost) on the board. 
+    /// </summary>
+    public struct Position {
+        public Position(Point Tile, Point DeltaPixel) {
+            this.Tile = Tile;
+            this.DeltaPixel = DeltaPixel;
+        }
+        /// <summary>
+        /// The tile the entity is on.
+        /// </summary>
+        public Point Tile;
+        /// <summary>
+        /// How many pixels the entity is off its nominal tile.
+        /// </summary>
+        public Point DeltaPixel;
+    }
+
+    public enum Direction { Up, Down, Left, Right };
+    public enum State { Start, Normal, Dying };
+
+    /// <summary>
+    /// This is the yellow pac man that eat dots and gets killed 
+    /// repetitively unless you're good.
+    /// </summary>
+    public class Player {
+
+        public Player(Game game) {
+            Reset();
+            this.game = game;
+            updatesPerPixel_ = Constants.PacManSpeed();
+            spriteBatch_ = (SpriteBatch)game.Services.GetService(typeof(SpriteBatch));
+            eatingFrames_ = new Texture2D[] {
+                game.Content.Load<Texture2D>("sprites/PacManEating1"),
+                game.Content.Load<Texture2D>("sprites/PacManEating2"),
+                game.Content.Load<Texture2D>("sprites/PacManEating3"),
+                game.Content.Load<Texture2D>("sprites/PacManEating4"),
+                game.Content.Load<Texture2D>("sprites/PacManEating5"),
+                game.Content.Load<Texture2D>("sprites/PacManEating6"),
+                game.Content.Load<Texture2D>("sprites/PacManEating7"),
+                game.Content.Load<Texture2D>("sprites/PacManEating8"),
+                game.Content.Load<Texture2D>("sprites/PacManEating9"),
+            };
+            dyingFrames_ = game.Content.Load<Texture2D>("sprites/DyingSheetNew");
+        }
+
+
+        /// <summary>
+        /// Allows the Player to update itself.
+        /// </summary>
+        /// <param name="gameTime">Provides a snapshot of timing values.</param>
+        public void Update(GameTime gameTime) {
+
+            // First, deal with keyboard input. Get only the direction keys.
+            Keys[] validKeys = { Keys.Up, Keys.Down, Keys.Left, Keys.Right };
+            Keys[] pressedKeys = (from k in Keyboard.GetState().GetPressedKeys()
+                                  where validKeys.Contains(k)
+                                  select k).ToArray();
+
+            // If the player is pressing more than one key, we don't turn. Trying to filter keys would be
+            // either bogus or overly complex for the needs of this game, and yes, this is from experience.
+            if (pressedKeys.Length == 1) {
+
+                // At level start, Pac Man is facing right, although he is drawn full. Pressing
+                // Right simply makes him start moving in that direction; pressing Left make him change direction
+                // first; this must be handled separately from the usual case.
+                if (state_ == State.Start) {
+                    if (pressedKeys[0] == Keys.Left || pressedKeys[0] == Keys.Right) {
+                        state_ = State.Normal;
+                    }
+                    if (pressedKeys[0] == Keys.Left) {
+                        TryTurn(pressedKeys[0]);
+                    }
+                }
+                // Normal case : turn if required direction != current direction.
+                else if ((direction_.ToString() != pressedKeys[0].ToString())) {
+                    TryTurn(pressedKeys[0]);
+                }
+            }
+
+            TryMove();
+        }
+
+        /// <summary>
+        /// Ensures that if the Pac Man moves, it is a legal move
+        /// </summary>
+        void TryMove() {
+            if (state_ == State.Start) {
+                return;
+            }
+            // If between two tiles, movement is always allowed since TryTurn()
+            // always ensures direction is valid
+            if (position_.DeltaPixel != Point.Zero) {
+                DoMove();
+            }
+            // Special case : the tunnel.
+            else if ((position_.Tile == new Point(0, 14) && direction_ == Direction.Left) ||
+                     (position_.Tile == new Point(27, 14) && direction_ == Direction.Right)) {
+                DoMove();
+            }
+            // If on a tile, we only move if the next tile in our direction is open
+            else if ((direction_ == Direction.Up && Grid.TileGrid[position_.Tile.X, position_.Tile.Y - 1].Type == TileTypes.Open) ||
+                      (direction_ == Direction.Down && Grid.TileGrid[position_.Tile.X, position_.Tile.Y + 1].Type == TileTypes.Open) ||
+                      (direction_ == Direction.Left && Grid.TileGrid[position_.Tile.X - 1, position_.Tile.Y].Type == TileTypes.Open) ||
+                      (direction_ == Direction.Right && Grid.TileGrid[position_.Tile.X + 1, position_.Tile.Y].Type == TileTypes.Open)) {
+                DoMove();
+            }
+
+        }
+
+
+        /// <summary>
+        /// Effectively moves the Pac Man according to member variable direction_.
+        /// </summary>
+        void DoMove() {
+            // Everytime the updateCount == updatesPerPixel, move one pixel in
+            // the desired direction and reset the updateCount.
+            updateCount_++;
+            updateCount_ %= updatesPerPixel_;
+            if (updateCount_ == 0) {
+
+                // Now is a nice time to update our speed, like we do for the ghosts.
+                if (Ghost.NextTile(direction_, position_).HasCrump) {
+                    updatesPerPixel_ = Constants.PacManSpeed() + 2;
+                }
+                else {
+                    updatesPerPixel_ = Constants.PacManSpeed();
+                }
+
+                // Move one pixel in the desired direction
+                if (direction_ == Direction.Up) {
+                    position_.DeltaPixel.Y--;
+                }
+                else if (direction_ == Direction.Down) {
+                    position_.DeltaPixel.Y++;
+                }
+                else if (direction_ == Direction.Left) {
+                    position_.DeltaPixel.X--;
+                }
+                else if (direction_ == Direction.Right) {
+                    position_.DeltaPixel.X++;
+                }
+
+                // By moving one pixel we might have moved to another tile completely
+                if (position_.DeltaPixel.X == 16) {
+                    // Special case : the tunnel
+                    if (position_.Tile.X == 27) {
+                        position_.Tile.X = 0;
+                    }
+                    else {
+                        position_.Tile.X++;
+                    }
+                    position_.DeltaPixel.X = 0;
+                }
+                else if (position_.DeltaPixel.X == -16) {
+                    // Special case : the tunnel
+                    if (position_.Tile.X == 0) {
+                        position_.Tile.X = 27;
+                    }
+                    else {
+                        position_.Tile.X--;
+                    }
+                    position_.DeltaPixel.X = 0;
+                }
+                else if (position_.DeltaPixel.Y == 16) {
+                    position_.Tile.Y++;
+                    position_.DeltaPixel.Y = 0;
+                }
+                else if (position_.DeltaPixel.Y == -16) {
+                    position_.Tile.Y--;
+                    position_.DeltaPixel.Y = 0;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Allows the Player to be drawn to the screen. Assumes spritebatch.begin() has been called, and
+        /// spritebatch.end() will be called afterwards.
+        /// </summary>
+        /// <param name="gameTime">Provides a snapshot of timing values.</param>
+        public void Draw(GameTime gameTime, Vector2 boardPosition) {
+            // The position is taken as a function of the board position, the tile on which the pac man is, how many
+            // pixels he is off from this tile, and the size of the pac man itself versus the size of a tile (16).
+            Vector2 position;
+            position.X = boardPosition.X + (position_.Tile.X * 16) + position_.DeltaPixel.X - ((eatingFrames_[0].Width - 16) / 2);
+            position.Y = boardPosition.Y + (position_.Tile.Y * 16) + position_.DeltaPixel.Y - ((eatingFrames_[0].Height - 16) / 2);
+
+            // At level start, just draw the full pac man and exit
+            if (state_ == State.Start) {
+                spriteBatch_.Draw(eatingFrames_[0], position, Color.White);
+            }
+
+            else if (state_ == State.Normal) {
+                // The frame index is taken as a function of how much the pac man is off from a tile, the size of
+                // a tile (always 16 pixels), and how many frames we use for the animation.
+                int frame = Math.Abs(position_.DeltaPixel.X + position_.DeltaPixel.Y) / (16 / usedFramesIndex_.Length);
+                frame = (int)MathHelper.Clamp(frame, 0, usedFramesIndex_.Length - 1);
+                RenderSprite(eatingFrames_[usedFramesIndex_[frame]], null, boardPosition, position);
+            }
+
+            else if (state_ == State.Dying) {
+                int timeBetweenFrames = 90; // Sound "Death" is 1811 milliseconds long, we have 20 frames to go through.
+                //timer_ += gameTime.ElapsedRealTime;
+                timer_ += gameTime.ElapsedGameTime;
+                int index = (timer_.Seconds * 1000 + timer_.Milliseconds) / timeBetweenFrames;
+                if (index > 19) {
+                    return;
+                }
+                RenderSprite(dyingFrames_, new Rectangle(26 * index, 0, 26, 26), boardPosition, position);
+            }
+
+        }
+
+
+        /// <summary>
+        /// Allows rendering across the tunnel, which is tricky.
+        /// </summary>
+        /// <param name="spriteSheet">Source texture</param>
+        /// <param name="rectangle">Portion of the source to render. Pass null for rendering the whole texture.</param>
+        /// <param name="boardPosition">Top-left pixel of the board in the screen</param>
+        /// <param name="position">Where to render the texture.</param>
+        void RenderSprite(Texture2D spriteSheet, Rectangle? rectangle, Vector2 boardPosition, Vector2 position) {
+
+            Rectangle rect = rectangle == null ? new Rectangle(0, 0, spriteSheet.Width, spriteSheet.Height) :
+                                                rectangle.Value;
+
+            // What happens when we are crossing to the other end by the tunnel?
+            // We detect if part of the pacman is rendered outside of the board.
+            // First, to the left.
+            if (position.X < boardPosition.X) {
+                int deltaPixel = (int)(boardPosition.X - position.X); // Number of pixels off the board
+                var leftPortion = new Rectangle(rect.X + deltaPixel, rect.Y, 26 - deltaPixel, 26);
+                var leftPortionPosition = new Vector2(boardPosition.X, position.Y);
+                var rightPortion = new Rectangle(rect.X, rect.Y, deltaPixel, 26);
+                var rightPortionPosition = new Vector2(boardPosition.X + (16 * 28) - deltaPixel, position.Y);
+                spriteBatch_.Draw(spriteSheet, leftPortionPosition, leftPortion, Color.White);
+                spriteBatch_.Draw(spriteSheet, rightPortionPosition, rightPortion, Color.White);
+            }
+            // Next, to the right
+            else if (position.X > (boardPosition.X + (16 * 28) - 26)) {
+                int deltaPixel = (int)((position.X + 26) - (boardPosition.X + (16 * 28))); // Number of pixels off the board
+                var leftPortion = new Rectangle(rect.X + 26 - deltaPixel, rect.Y, deltaPixel, 26);
+                var leftPortionPosition = new Vector2(boardPosition.X, position.Y);
+                var rightPortion = new Rectangle(rect.X, rect.Y, 26 - deltaPixel, 26);
+                var rightPortionPosition = new Vector2(position.X, position.Y);
+                spriteBatch_.Draw(spriteSheet, leftPortionPosition, leftPortion, Color.White);
+                spriteBatch_.Draw(spriteSheet, rightPortionPosition, rightPortion, Color.White);
+            }
+            // Draw normally - in one piece
+            else {
+                spriteBatch_.Draw(spriteSheet, position, rect, Color.White);
+            }
+        }
+
+        /// <summary>
+        /// Should be called anytime the Pac Man needs to be reset (game start, level start)
+        /// </summary>
+        void Reset() {
+            state_ = State.Start;
+            direction_ = Direction.Right;
+            usedFramesIndex_ = new int[] { 0, 1, 2 };
+            position_ = new Position { Tile = new Point(13, 23), DeltaPixel = new Point(8, 0) };
+            updateCount_ = 0;
+        }
+
+
+
+        /// <summary>
+        /// Ensures that if the Pac Man turns, it's in a valid direction.
+        /// </summary>
+        /// <param name="input">Direction the player tries to steer the Pac Man towards</param>
+        void TryTurn(Keys input) {
+
+            // If we're between two tiles, we can only turn 180
+            if (position_.DeltaPixel != Point.Zero) {
+                if ((direction_ == Direction.Up && input == Keys.Down) ||
+                    (direction_ == Direction.Down && input == Keys.Up) ||
+                    (direction_ == Direction.Left && input == Keys.Right) ||
+                    (direction_ == Direction.Right && input == Keys.Left)) {
+                    // Turning 180 between two tiles implies destination tile is open, 
+                    // no other validation to be done
+                    DoTurn(input);
+                }
+            }
+            // Special case : the tunnel.
+            else if ((input == Keys.Left && position_.Tile.X == 0) ||
+                      (input == Keys.Right && position_.Tile.X == 27)) {
+                DoTurn(input);
+            }
+            // We're exactly on a tile; this is the "general" case
+            // Do turn if the destination tile is open
+            else if ((input == Keys.Up && Grid.TileGrid[position_.Tile.X, position_.Tile.Y - 1].Type == TileTypes.Open) ||
+                      (input == Keys.Down && Grid.TileGrid[position_.Tile.X, position_.Tile.Y + 1].Type == TileTypes.Open) ||
+                      (input == Keys.Left && Grid.TileGrid[position_.Tile.X - 1, position_.Tile.Y].Type == TileTypes.Open) ||
+                      (input == Keys.Right && Grid.TileGrid[position_.Tile.X + 1, position_.Tile.Y].Type == TileTypes.Open)) {
+                DoTurn(input);
+            }
+
+        }
+
+
+        /// <summary>
+        /// This effectively makes Pac Man turn.
+        /// We have to update the sprites used for animation,
+        /// and if the Pac Man is between two tiles, change his Position.
+        /// </summary>
+        /// <param name="newDirection">Direction to turn towards</param>
+        void DoTurn(Keys newDirection) {
+
+            switch (newDirection) {
+                case Keys.Up:
+                    direction_ = Direction.Up;
+                    usedFramesIndex_ = new int[] { 0, 7, 8 };
+                    if (position_.DeltaPixel != Point.Zero) {
+                        position_.Tile.Y += 1;
+                        position_.DeltaPixel.Y -= 16;
+                    }
+                    break;
+                case Keys.Down:
+                    direction_ = Direction.Down;
+                    usedFramesIndex_ = new int[] { 0, 3, 4 };
+                    if (position_.DeltaPixel != Point.Zero) {
+                        position_.Tile.Y -= 1;
+                        position_.DeltaPixel.Y += 16;
+                    }
+                    break;
+                case Keys.Left:
+                    direction_ = Direction.Left;
+                    usedFramesIndex_ = new int[] { 0, 5, 6 };
+                    if (position_.DeltaPixel != Point.Zero) {
+                        position_.Tile.X += 1;
+                        position_.DeltaPixel.X -= 16;
+                    }
+                    break;
+                case Keys.Right:
+                    direction_ = Direction.Right;
+                    usedFramesIndex_ = new int[] { 0, 1, 2 };
+                    if (position_.DeltaPixel != Point.Zero) {
+                        position_.Tile.X -= 1;
+                        position_.DeltaPixel.X += 16;
+                    }
+                    break;
+            }
+        }
+
+        Game game;
+        TimeSpan timer_;
+        SpriteBatch spriteBatch_;
+        Texture2D dyingFrames_;
+        Texture2D[] eatingFrames_;
+        int[] usedFramesIndex_;
+        int updatesPerPixel_;
+        int updateCount_;
+        Position position_;
+        Direction direction_;
+        State state_;
+
+        public State State { get { return state_; } set { state_ = value; } }
+        public Direction Direction { get { return direction_; } }
+        public Position Position { get { return position_; } }
+        
+    }
+}

+ 38 - 0
Samples/MacOS/XNAPacMan/Program.cs

@@ -0,0 +1,38 @@
+using System;
+
+namespace XNAPacMan {
+
+	static class Program {
+		/// <summary>
+		/// The main entry point for the application.
+		/// </summary>
+		static void Main (string[] args)
+		{
+			MonoMac.AppKit.NSApplication.Init ();
+			
+			using (var p = new MonoMac.Foundation.NSAutoreleasePool ()) {
+				MonoMac.AppKit.NSApplication.SharedApplication.Delegate = new AppDelegate ();
+				MonoMac.AppKit.NSApplication.Main (args);
+			}
+		}
+
+	
+		class AppDelegate : MonoMac.AppKit.NSApplicationDelegate
+		{
+
+			XNAPacMan game;
+			public override void FinishedLaunching (MonoMac.Foundation.NSObject notification)
+			{
+				game = new XNAPacMan();
+				game.Run();
+			}
+		
+			public override bool ApplicationShouldTerminateAfterLastWindowClosed (MonoMac.AppKit.NSApplication sender)
+			{
+				return true;
+			}
+		}
+	}
+
+}
+

+ 86 - 0
Samples/MacOS/XNAPacMan/XNAPacMan.cs

@@ -0,0 +1,86 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+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.Media;
+using Microsoft.Xna.Framework.Net;
+using Microsoft.Xna.Framework.Storage;
+
+namespace XNAPacMan {
+    /// <summary>
+    /// This is the main type for your game
+    /// </summary>
+    public class XNAPacMan : Microsoft.Xna.Framework.Game {
+        GraphicsDeviceManager graphics_;
+        SpriteBatch spriteBatch_;
+        AudioEngine audioEngine_;
+        WaveBank waveBank_;
+        SoundBank soundBank_;
+
+        public XNAPacMan() {
+            // Pac Man 2 is somewhat resolution-independent, but runs best at 720x640.
+            graphics_ = new GraphicsDeviceManager(this);
+            graphics_.PreferredBackBufferHeight = 720;
+            graphics_.PreferredBackBufferWidth = 640;
+
+            // Pac Man 2 always updates 1000 times per second. Framerate may vary.
+            IsFixedTimeStep = true;
+            TargetElapsedTime = TimeSpan.FromMilliseconds(1);
+
+            // The menu needs to be added in the Components list in the constructor.
+            // Otherwise its Initialize() method is not called and everything blows up.
+            Components.Add(new Menu(this, null));
+
+            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() {
+            // This will be called before the Initialize() method of any component, which
+            // all rely on these Services being available.
+            audioEngine_ = new AudioEngine("Content/Audio/YEPAudio.xgs");
+            waveBank_ = new WaveBank(audioEngine_, "Content/Audio/Wave Bank.xwb");
+            soundBank_ = new SoundBank(audioEngine_, "Content/Audio/Sound Bank.xsb");
+            Services.AddService(typeof(AudioEngine), audioEngine_);
+            Services.AddService(typeof(SoundBank), soundBank_);
+
+            spriteBatch_ = new SpriteBatch(GraphicsDevice);
+            Services.AddService(typeof(SpriteBatch), spriteBatch_);
+            Services.AddService(typeof(GraphicsDeviceManager), graphics_);
+            base.Initialize();
+        }
+
+        /// <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) {
+            // The components are automatically updated by XNA. The only relevant
+            // task here is to update the AudioEngine.
+            audioEngine_.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) {
+            // We _always_ clear to black, so we do this here.
+            GraphicsDevice.Clear(Color.Black);
+
+            base.Draw(gameTime);
+        }
+    }
+}

+ 154 - 0
Samples/MacOS/XNAPacMan/XNAPacMan.csproj

@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProductVersion>10.0.0</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{557C7051-DFEB-4F19-A706-102574443BCB}</ProjectGuid>
+    <ProjectTypeGuids>{948B3504-5B70-4649-8FE4-BDE1FB46EC69};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <OutputType>Exe</OutputType>
+    <RootNamespace>XNAPacMan</RootNamespace>
+    <AssemblyName>XNAPacMan</AssemblyName>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug</OutputPath>
+    <DefineConstants>DEBUG;</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <ConsolePause>false</ConsolePause>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>none</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Release</OutputPath>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <ConsolePause>false</ConsolePause>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="System.Xml" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="MonoMac" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="Info.plist" />
+    <None Include="Game.ico" />
+    <None Include="GameThumbnail.png" />
+    <None Include="Content\bonus\Apple.png" />
+    <None Include="Content\bonus\Banana.png" />
+    <None Include="Content\bonus\Bell.png" />
+    <None Include="Content\bonus\Cherry.png" />
+    <None Include="Content\bonus\Key.png" />
+    <None Include="Content\bonus\Orange.png" />
+    <None Include="Content\bonus\Pear.png" />
+    <None Include="Content\bonus\Pretzel.png" />
+    <None Include="Content\bonus\Strawberry.png" />
+    <None Include="Content\sprites\Board.png" />
+    <None Include="Content\sprites\BoardFlash.png" />
+    <None Include="Content\sprites\Crump.png" />
+    <None Include="Content\sprites\DyingSheetNew.png" />
+    <None Include="Content\sprites\ExtraLife.png" />
+    <None Include="Content\sprites\GhostBase.png" />
+    <None Include="Content\sprites\GhostBase2.png" />
+    <None Include="Content\sprites\GhostChased.png" />
+    <None Include="Content\sprites\GhostEyes.png" />
+    <None Include="Content\sprites\GhostEyesCenter.png" />
+    <None Include="Content\sprites\PacManEating1.png" />
+    <None Include="Content\sprites\PacManEating2.png" />
+    <None Include="Content\sprites\PacManEating3.png" />
+    <None Include="Content\sprites\PacManEating4.png" />
+    <None Include="Content\sprites\PacManEating5.png" />
+    <None Include="Content\sprites\PacManEating6.png" />
+    <None Include="Content\sprites\PacManEating7.png" />
+    <None Include="Content\sprites\PacManEating8.png" />
+    <None Include="Content\sprites\PacManEating9.png" />
+    <None Include="Content\sprites\PowerPill.png" />
+    <None Include="Content\sprites\Selection.png" />
+    <None Include="Content\sprites\Title.png" />
+  </ItemGroup>
+  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+  <Import Project="$(MSBuildExtensionsPath)\Mono\MonoMac\v0.0\Mono.MonoMac.targets" />
+  <ItemGroup>
+    <ProjectReference Include="..\..\..\..\MonoGame\MonoGame.Framework\MonoGame.Framework.MacOS.csproj">
+      <Project>{36C538E6-C32A-4A8D-A39C-566173D7118E}</Project>
+      <Name>MonoGame.Framework.MacOS</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Constants.cs" />
+    <Compile Include="GameLoop.cs" />
+    <Compile Include="Ghost.cs" />
+    <Compile Include="GhostSoundsManager.cs" />
+    <Compile Include="Grid.cs" />
+    <Compile Include="HighScores.cs" />
+    <Compile Include="Menu.cs" />
+    <Compile Include="Player.cs" />
+    <Compile Include="Program.cs" />
+    <Compile Include="XNAPacMan.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <Folder Include="Content\" />
+  </ItemGroup>
+  <ItemGroup>
+    <Content Include="Content\Audio\XNAPacManAudio.xap" />
+    <Content Include="Content\Audio\XNAPacManAudio.xml" />
+    <Content Include="Content\Audio\bg1.wav" />
+    <Content Include="Content\Audio\bg2.wav" />
+    <Content Include="Content\Audio\bg3.wav" />
+    <Content Include="Content\Audio\bgghost.wav" />
+    <Content Include="Content\Audio\bgghosteyes.wav" />
+    <Content Include="Content\Audio\eatfruit.wav" />
+    <Content Include="Content\Audio\eatghost.wav" />
+    <Content Include="Content\Audio\eatpill1.wav" />
+    <Content Include="Content\Audio\eatpill2.wav" />
+    <Content Include="Content\Audio\extralife.wav" />
+    <Content Include="Content\Audio\killed.wav" />
+    <Content Include="Content\Audio\newgame.wav" />
+    <Content Include="Content\Audio\newlevel.wav" />
+    <Content Include="Content\Audio\Sound Bank.xsb" />
+    <Content Include="Content\Audio\Wave Bank.xwb" />
+    <Content Include="Content\Audio\YEPAudio.xgs" />
+    <Content Include="Content\bonus\Apple.xnb" />
+    <Content Include="Content\bonus\Banana.xnb" />
+    <Content Include="Content\bonus\Bell.xnb" />
+    <Content Include="Content\bonus\Cherry.xnb" />
+    <Content Include="Content\bonus\Key.xnb" />
+    <Content Include="Content\bonus\Orange.xnb" />
+    <Content Include="Content\bonus\Pear.xnb" />
+    <Content Include="Content\bonus\Pretzel.xnb" />
+    <Content Include="Content\bonus\Strawberry.xnb" />
+    <Content Include="Content\sprites\Board.xnb" />
+    <Content Include="Content\sprites\BoardFlash.xnb" />
+    <Content Include="Content\sprites\Crump.xnb" />
+    <Content Include="Content\sprites\DyingSheetNew.xnb" />
+    <Content Include="Content\sprites\ExtraLife.xnb" />
+    <Content Include="Content\sprites\GhostBase.xnb" />
+    <Content Include="Content\sprites\GhostBase2.xnb" />
+    <Content Include="Content\sprites\GhostChased.xnb" />
+    <Content Include="Content\sprites\GhostEyes.xnb" />
+    <Content Include="Content\sprites\GhostEyesCenter.xnb" />
+    <Content Include="Content\sprites\PacManEating1.xnb" />
+    <Content Include="Content\sprites\PacManEating2.xnb" />
+    <Content Include="Content\sprites\PacManEating3.xnb" />
+    <Content Include="Content\sprites\PacManEating4.xnb" />
+    <Content Include="Content\sprites\PacManEating5.xnb" />
+    <Content Include="Content\sprites\PacManEating6.xnb" />
+    <Content Include="Content\sprites\PacManEating7.xnb" />
+    <Content Include="Content\sprites\PacManEating8.xnb" />
+    <Content Include="Content\sprites\PacManEating9.xnb" />
+    <Content Include="Content\sprites\PowerPill.xnb" />
+    <Content Include="Content\sprites\Selection.xnb" />
+    <Content Include="Content\sprites\Title.xnb" />
+    <Content Include="Content\Grid.txt" />
+    <Content Include="Content\MenuItem.xnb" />
+    <Content Include="Content\Score.xnb" />
+    <Content Include="Content\ScoreEvent.xnb" />
+  </ItemGroup>
+</Project>

+ 20 - 0
Samples/MonoGame.Samples.MacOS.sln

@@ -83,6 +83,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatapaultGame", "MacOS\Cata
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatapultNetWars", "MacOS\CatapultNetWars\CatapultNetWars.csproj", "{1F4BF87D-34DA-417D-96DE-24213A05739B}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XNAPacMan", "MacOS\XNAPacMan\XNAPacMan.csproj", "{557C7051-DFEB-4F19-A706-102574443BCB}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -314,6 +316,24 @@ Global
 		{52735207-2136-433B-A3E4-4C082728EED8}.Release|Any CPU.Build.0 = Release|Any CPU
 		{52735207-2136-433B-A3E4-4C082728EED8}.Release|x86.ActiveCfg = Release|Any CPU
 		{52735207-2136-433B-A3E4-4C082728EED8}.Release|x86.Build.0 = Release|Any CPU
+		{557C7051-DFEB-4F19-A706-102574443BCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{557C7051-DFEB-4F19-A706-102574443BCB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{557C7051-DFEB-4F19-A706-102574443BCB}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+		{557C7051-DFEB-4F19-A706-102574443BCB}.Debug|iPhone.Build.0 = Debug|Any CPU
+		{557C7051-DFEB-4F19-A706-102574443BCB}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{557C7051-DFEB-4F19-A706-102574443BCB}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{557C7051-DFEB-4F19-A706-102574443BCB}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{557C7051-DFEB-4F19-A706-102574443BCB}.Debug|x86.Build.0 = Debug|Any CPU
+		{557C7051-DFEB-4F19-A706-102574443BCB}.Distribution|Any CPU.ActiveCfg = Debug|Any CPU
+		{557C7051-DFEB-4F19-A706-102574443BCB}.Distribution|Any CPU.Build.0 = Debug|Any CPU
+		{557C7051-DFEB-4F19-A706-102574443BCB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{557C7051-DFEB-4F19-A706-102574443BCB}.Release|Any CPU.Build.0 = Release|Any CPU
+		{557C7051-DFEB-4F19-A706-102574443BCB}.Release|iPhone.ActiveCfg = Release|Any CPU
+		{557C7051-DFEB-4F19-A706-102574443BCB}.Release|iPhone.Build.0 = Release|Any CPU
+		{557C7051-DFEB-4F19-A706-102574443BCB}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{557C7051-DFEB-4F19-A706-102574443BCB}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+		{557C7051-DFEB-4F19-A706-102574443BCB}.Release|x86.ActiveCfg = Release|Any CPU
+		{557C7051-DFEB-4F19-A706-102574443BCB}.Release|x86.Build.0 = Release|Any CPU
 		{56C67234-2829-4AB0-8FEE-C8D01617CDCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{56C67234-2829-4AB0-8FEE-C8D01617CDCE}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{56C67234-2829-4AB0-8FEE-C8D01617CDCE}.Debug|x86.ActiveCfg = Debug|Any CPU