// File: Game.cs
// Author: Sonhui

using System;
using System.Collections;
using System.Drawing;
// File: Game.cs
// Author: Sonhui Schweitzer
// Date: January 25th, 2005
// Project: CS470 Applied Software Developement Project
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Direct3D=Microsoft.DirectX.Direct3D;
using Microsoft.DirectX.DirectSound;
using DirectSound=Microsoft.DirectX.DirectSound;
using Microsoft.DirectX.DirectInput;
using DirectInput=Microsoft.DirectX.DirectInput;

namespace BreakOut
{
	public class Game
	{
		private Random m_oRandom = new Random( ( int )DateTime.Now.Ticks );
		private ColorValue m_oBackgroundColor;

		// entity collections
		private ArrayList m_oWalls = new ArrayList();
		private ArrayList m_oBreakables = new ArrayList();
		private ArrayList m_oPaddles = new ArrayList();
		private ArrayList m_oPowerUps = new ArrayList();
		private ArrayList m_oBalls = new ArrayList();
		private ArrayList m_oVoids = new ArrayList();
		private ArrayList m_oParticleSystems = new ArrayList();
		private ArrayList m_oProjectiles = new ArrayList();
	
		// game variables
		private int m_iCurrentLevel = 0;
		private int m_iLevelCount = 3;
		private float m_fInitialBallSpeedIncrement = 0.1f;
		private float m_fInitialBallSpeed = 1.0f;
		private float m_fGrowthIncrement = 0.001f;
		private float m_fGrowthRate = 0.001f;
		private int m_iPlayerBalls = 5;
		private int m_iPlayerScore = 0;
		private bool m_bGameOver = true;

		// device resource collections
		private Hashtable m_oCollisions = new Hashtable();
		private Hashtable m_oSounds = new Hashtable();
		private Hashtable m_oVertexBuffers = new Hashtable();
		private Hashtable m_oTextures = new Hashtable();

		// properties
		public float GrowthRateIncrement { get { return m_fGrowthIncrement; } set { m_fGrowthIncrement = value; } }
		public float GrowthRate { get { return m_fGrowthRate; } set { m_fGrowthRate = value; } }
		public float InitialBallSpeedIncrement { get { return m_fInitialBallSpeedIncrement; } set { m_fInitialBallSpeedIncrement = value; } }
		public float InitialBallSpeed { get { return m_fInitialBallSpeed; } set { m_fInitialBallSpeed = value; } }
		public bool GameOver { get { return m_bGameOver; } }
		public Random Random { get { return m_oRandom; } }
		public int BackGroundColor { get { return m_oBackgroundColor.ToArgb(); } }
		public int PlayerBalls { get { return m_iPlayerBalls; } set { m_iPlayerBalls = value; } }
		public int PlayerScore { get { return m_iPlayerScore; } set { m_iPlayerScore = value; } }

		// constructor
		public Game()
		{
			// setup some variables
			m_oRandom = new Random( DateTime.Now.Millisecond );
			m_oBackgroundColor = new ColorValue( 0.0f , 0.0f , 0.0f , 1.0f );
		}

		// wall collection methods
		public Wall GetWall( int iWall ) 
		{ 
			return ( Wall )m_oWalls[ iWall ]; 
		}
		public int GetWallCount() 
		{ 
			return m_oWalls.Count; 
		}
		public void AddWall( Wall oWall ) 
		{ 
			m_oWalls.Add( oWall );
		}
		public void RemoveWall( Wall oWall ) 
		{ 
			m_oWalls.Remove( oWall );
		}

		// ball collection methods
		public Ball GetBall( int iBall ) 
		{ 
			return ( Ball )m_oBalls[ iBall ]; 
		}
		public int GetBallCount() 
		{ 
			return m_oBalls.Count; 
		}
		public void AddBall( Ball oBall ) 
		{ 
			m_oBalls.Add( oBall ); 
		}
		public void RemoveBall( Ball oBall ) 
		{ 
			m_oBalls.Remove( oBall );
		}

		// breakable collection methods
		public Breakable GetBreakable( int iBreakable ) 
		{ 
			return ( Breakable )m_oBreakables[ iBreakable ]; 
		}
		public int GetBreakableCount() 
		{ 
			return m_oBreakables.Count; 
		}
		public void AddBreakable( Breakable oBreakable ) 
		{ 
			m_oBreakables.Add( oBreakable ); 
		}
		public void RemoveBreakable( Breakable oBreakable ) 
		{ 
			m_oBreakables.Remove( oBreakable );
		}

		// paddle collection methods
		public Paddle GetPaddle( int iPaddle ) 
		{ 
			return ( Paddle )m_oPaddles[ iPaddle ]; 
		}
		public int GetPaddleCount() 
		{ 
			return m_oPaddles.Count; 
		}
		public void AddPaddle( Paddle oPaddle ) 
		{ 
			m_oPaddles.Add( oPaddle ); 
		}
		public void RemovePaddle( Paddle oPaddle ) 
		{ 
			m_oPaddles.Remove( oPaddle );
		}

		// particle system collection methods
		public ParticleSystem GetParticleSystem( int iParticleSystem ) 
		{ 
			return ( ParticleSystem )m_oParticleSystems[ iParticleSystem ]; 
		}
		public int GetParticleSystemCount() 
		{ 
			return m_oParticleSystems.Count; 
		}
		public void AddParticleSystem( ParticleSystem oParticleSystem ) 
		{ 
			m_oParticleSystems.Add( oParticleSystem ); 
		}
		public void RemoveParticleSystem( ParticleSystem oParticleSystem ) 
		{ 
			m_oParticleSystems.Remove( oParticleSystem );
		}

		// power up collection methods
		public PowerUp GetPowerUp( int iPowerUp ) 
		{ 
			return ( PowerUp )m_oPowerUps[ iPowerUp ]; 
		}
		public int GetPowerUpCount() 
		{ 
			return m_oPowerUps.Count; 
		}
		public void AddPowerUp( PowerUp oPowerUp ) 
		{ 
			m_oPowerUps.Add( oPowerUp ); 
		}
		public void RemovePowerUp( PowerUp oPowerUp ) 
		{ 
			m_oPowerUps.Remove( oPowerUp );
		}

		// void collection methods
		public Void GetVoid( int iVoid ) 
		{ 
			return ( Void )m_oVoids[ iVoid ]; 
		}
		public int GetVoidCount() 
		{ 
			return m_oVoids.Count; 
		}
		public void AddVoid( Void oVoid ) 
		{ 
			m_oVoids.Add( oVoid ); 
		}
		public void RemoveVoid( Void oVoid ) 
		{ 
			m_oVoids.Remove( oVoid );
		}

		// void collection methods
		public Projectile GetProjectile( int iProjectile ) 
		{ 
			return ( Projectile )m_oProjectiles[ iProjectile ]; 
		}
		public int GetProjectileCount() 
		{ 
			return m_oProjectiles.Count; 
		}
		public void AddProjectile( Projectile oProjectile ) 
		{ 
			m_oProjectiles.Add( oProjectile ); 
		}
		public void RemoveProjectile( Projectile oProjectile ) 
		{ 
			m_oProjectiles.Remove( oProjectile );
		}

		// creates the DirectSound device resources
		public void InitializeSound( DirectSound.Device oDevice )
		{
			// load a few of each type so more than 1 can be played
			// at one time
			m_oSounds[ "Bounce" ]  = new object[ 8 ] { new DirectSound.SecondaryBuffer( "Bounce.wav"  , oDevice ) , new DirectSound.SecondaryBuffer( "Bounce.wav"  , oDevice ) , new DirectSound.SecondaryBuffer( "Bounce.wav"  , oDevice ) , new DirectSound.SecondaryBuffer( "Bounce.wav"  , oDevice ) , new DirectSound.SecondaryBuffer( "Bounce.wav"  , oDevice ) , new DirectSound.SecondaryBuffer( "Bounce.wav"  , oDevice ) , new DirectSound.SecondaryBuffer( "Bounce.wav"  , oDevice ) , new DirectSound.SecondaryBuffer( "Bounce.wav"  , oDevice ) };
			m_oSounds[ "Pop" ]     = new object[ 8 ] { new DirectSound.SecondaryBuffer( "Pop.wav"     , oDevice ) , new DirectSound.SecondaryBuffer( "Pop.wav"     , oDevice ) , new DirectSound.SecondaryBuffer( "Pop.wav"     , oDevice ) , new DirectSound.SecondaryBuffer( "Pop.wav"     , oDevice ) , new DirectSound.SecondaryBuffer( "Pop.wav"     , oDevice ) , new DirectSound.SecondaryBuffer( "Pop.wav"     , oDevice ) , new DirectSound.SecondaryBuffer( "Pop.wav"     , oDevice ) , new DirectSound.SecondaryBuffer( "Pop.wav"     , oDevice ) };
			m_oSounds[ "Explode" ] = new object[ 8 ] { new DirectSound.SecondaryBuffer( "Explode.wav" , oDevice ) , new DirectSound.SecondaryBuffer( "Explode.wav" , oDevice ) , new DirectSound.SecondaryBuffer( "Explode.wav" , oDevice ) , new DirectSound.SecondaryBuffer( "Explode.wav" , oDevice ) , new DirectSound.SecondaryBuffer( "Explode.wav" , oDevice ) , new DirectSound.SecondaryBuffer( "Explode.wav" , oDevice ) , new DirectSound.SecondaryBuffer( "Explode.wav" , oDevice ) , new DirectSound.SecondaryBuffer( "Explode.wav" , oDevice ) };
			m_oSounds[ "PowerUp" ] = new object[ 8 ] { new DirectSound.SecondaryBuffer( "PowerUp.wav" , oDevice ) , new DirectSound.SecondaryBuffer( "PowerUp.wav" , oDevice ) , new DirectSound.SecondaryBuffer( "PowerUp.wav" , oDevice ) , new DirectSound.SecondaryBuffer( "PowerUp.wav" , oDevice ) , new DirectSound.SecondaryBuffer( "PowerUp.wav" , oDevice ) , new DirectSound.SecondaryBuffer( "PowerUp.wav" , oDevice ) , new DirectSound.SecondaryBuffer( "PowerUp.wav" , oDevice ) , new DirectSound.SecondaryBuffer( "PowerUp.wav" , oDevice ) };
			m_oSounds[ "Fire" ]    = new object[ 8 ] { new DirectSound.SecondaryBuffer( "Fire.wav"    , oDevice ) , new DirectSound.SecondaryBuffer( "Fire.wav"    , oDevice ) , new DirectSound.SecondaryBuffer( "Fire.wav"    , oDevice ) , new DirectSound.SecondaryBuffer( "Fire.wav"    , oDevice ) , new DirectSound.SecondaryBuffer( "Fire.wav"    , oDevice ) , new DirectSound.SecondaryBuffer( "Fire.wav"    , oDevice ) , new DirectSound.SecondaryBuffer( "Fire.wav"    , oDevice ) , new DirectSound.SecondaryBuffer( "Fire.wav"    , oDevice ) };
			m_oSounds[ "Lost" ]    = new object[ 8 ] { new DirectSound.SecondaryBuffer( "Lost.wav"    , oDevice ) , new DirectSound.SecondaryBuffer( "Lost.wav"    , oDevice ) , new DirectSound.SecondaryBuffer( "Lost.wav"    , oDevice ) , new DirectSound.SecondaryBuffer( "Lost.wav"    , oDevice ) , new DirectSound.SecondaryBuffer( "Lost.wav"    , oDevice ) , new DirectSound.SecondaryBuffer( "Lost.wav"    , oDevice ) , new DirectSound.SecondaryBuffer( "Lost.wav"    , oDevice ) , new DirectSound.SecondaryBuffer( "Lost.wav"    , oDevice ) };
		}

		// cleans up the DirectSound device resources
		public void ShutdownSound()
		{
			// loop over all the sound arrays
			foreach ( DictionaryEntry oEntry in m_oSounds )
			{
				object[] oBuffers = ( object[] )oEntry.Value;
				// loop over each sound in the array
				for ( int i = 0 ; i < oBuffers.Length ; ++i )
				{
					// release the sound
					SecondaryBuffer oBuffer = ( SecondaryBuffer )oBuffers[ i ];
					oBuffer.Dispose();
				}
			}
			// clear out the sounds since they are disposed
			m_oSounds.Clear();
		}

		// loads the currently selected level
		private void LoadLevel()
		{
			switch ( m_iCurrentLevel )
			{
				case 0:
					LoadLevel1();
					break;
				case 1:
					LoadLevel2();
					break;
				case 2:
					LoadLevel3();
					break;
			}
		}

		// creates the entities for level 1
		private void LoadLevel1()
		{
			// clean up any entities that remain
			ClearAll();

			// add walls
			AddWall( new Wall( new Vector2( -1.92f , 1.42f ) , new Vector2( 1.92f , 1.42f ) , false ) );
			AddWall( new Wall( new Vector2( 1.92f , 1.42f ) , new Vector2( 1.92f , -1.5f ) , false ) );
			AddWall( new Wall( new Vector2( -1.92f , -1.5f ) , new Vector2( -1.92f , 1.42f ) , false ) );

			// add voids
			AddVoid( new Void( new Vector2( 1.92f , -1.5f ) , new Vector2( -1.92f , -1.5f ) ) );

			// add breakables
			for ( float fY = -0.25f ; fY <= 1.25f ; fY += 0.25f )
			{
				for ( float fX = -1.5f ; fX <= 1.5f ; fX += 0.25f )
				{
					AddBreakable( new Breakable( new Vector2( fX , fY ) , 0.08f , Color.Blue , 0.02f , GeneratePowerUp() , m_fGrowthRate ) );
				}
			}

			// add a paddle
			Paddle oPaddle = new Paddle( 0.5f , new Vector2( 1.8f , -1.2f ) , new Vector2[ 1 ] { new Vector2( -3.6f , 0.0f ) } , 0.6f , 0.7f );
			AddPaddle( oPaddle );
			// give the paddle an initial ball using the initial speed variable
			oPaddle.Ball = new Ball( this , ( ( Line )oPaddle.GetBounds() ).Center + new Vector2( 0.0f , 0.04f ) , new Vector2( 0.0f , m_fInitialBallSpeed ) , Vector2.Empty );
		}

		// creates the entities for level 2
		private void LoadLevel2()
		{
			// clean up any entities that remain
			ClearAll();

			// add walls
			AddWall( new Wall( new Vector2( -1.92f , 1.42f ) , new Vector2( 1.92f , 1.42f ) , false ) );
			AddWall( new Wall( new Vector2( 1.92f , 1.42f ) , new Vector2( 1.92f , -1.5f ) , false ) );
			AddWall( new Wall( new Vector2( -1.92f , -1.5f ) , new Vector2( -1.92f , 1.42f ) , false ) );

			// add voids
			AddVoid( new Void( new Vector2( 1.92f , -1.5f ) , new Vector2( -1.92f , -1.5f ) ) );

			// add breakables
			for ( float fY = -0.25f ; fY <= 1.25f ; fY += 1.0f )
			{
				for ( float fX = -1.5f ; fX <= 1.5f ; fX += 1.0f )
				{
					AddBreakable( new Breakable( new Vector2( fX , fY ) , 0.2f , Color.Blue , 0.02f , GeneratePowerUp() , m_fGrowthRate ) );
				}
			}
			for ( float fX = -1.8f ; fX <= 1.8f ; fX += 3.6f / 64.0f )
			{
				AddBreakable( new Breakable( new Vector2( fX , 1.4f ) , ( 3.6f / 64.0f ) / 3.0f , Color.Blue , ( 3.6f / 64.0f ) / 3.0f , GeneratePowerUp() , m_fGrowthRate ) );
			}

			// add a paddle
			Paddle oPaddle = new Paddle( 0.5f , new Vector2( 1.8f , -1.2f ) , new Vector2[ 1 ] { new Vector2( -3.6f , 0.0f ) } , 0.6f , 0.7f );
			AddPaddle( oPaddle );
			// give the paddle an initial ball using the initial speed variable
			oPaddle.Ball = new Ball( this , ( ( Line )oPaddle.GetBounds() ).Center + new Vector2( 0.0f , 0.04f ) , new Vector2( 0.0f , m_fInitialBallSpeed ) , Vector2.Empty );
		}

		// creates the entities for level 3
		private void LoadLevel3()
		{
			// clean up any entities that remain
			ClearAll();

			// add walls
			AddWall( new Wall( new Vector2( -1.92f , 1.42f ) , new Vector2( 1.92f , 1.42f ) , false ) );
			AddWall( new Wall( new Vector2( 1.92f , 1.42f ) , new Vector2( 1.92f , -1.5f ) , false ) );
			AddWall( new Wall( new Vector2( -1.92f , -1.5f ) , new Vector2( -1.92f , 1.42f ) , false ) );
			for ( float fY = -0.6f ; fY <= 1.2f ; fY += 0.8f )
			{
				for ( float fX = -1.5f ; fX <= 1.5f ; fX += 0.5f )
				{
					AddWall( new Wall( new Vector2( fX - 0.1f , fY + 0.1f ) , new Vector2( fX - 0.1f , fY - 0.1f ) , true ) );
					AddWall( new Wall( new Vector2( fX - 0.1f , fY - 0.1f ) , new Vector2( fX + 0.1f , fY - 0.1f ) , true ) );
					AddWall( new Wall( new Vector2( fX + 0.1f , fY - 0.1f ) , new Vector2( fX + 0.1f , fY + 0.1f ) , true ) );
					AddWall( new Wall( new Vector2( fX + 0.1f , fY + 0.1f ) , new Vector2( fX - 0.1f , fY + 0.1f ) , true ) );
				}
			}

			// add voids
			AddVoid( new Void( new Vector2( 1.92f , -1.5f ) , new Vector2( -1.92f , -1.5f ) ) );

			// add breakables
			for ( float fY = -0.6f ; fY <= 1.2f ; fY += 0.2f )
			{
				for ( float fX = -1.25f ; fX <= 1.25f ; fX += 0.5f )
				{
					AddBreakable( new Breakable( new Vector2( fX , fY ) , 0.05f , Color.Blue , 0.02f , GeneratePowerUp() , m_fGrowthRate ) );
				}
			}

			// add a paddle
			Paddle oPaddle = new Paddle( 0.5f , new Vector2( 1.8f , -1.2f ) , new Vector2[ 1 ] { new Vector2( -3.6f , 0.0f ) } , 0.6f , 0.7f );
			AddPaddle( oPaddle );
			// give the paddle an initial ball using the initial speed variable
			oPaddle.Ball = new Ball( this , ( ( Line )oPaddle.GetBounds() ).Center + new Vector2( 0.0f , 0.04f ) , new Vector2( 0.0f , m_fInitialBallSpeed ) , Vector2.Empty );
		}

		// randomly generates a powerup
		private PowerUp GeneratePowerUp()
		{
			// 40% chance of generating a power up
			if ( m_oRandom.NextDouble() < 0.4 )
			{
				switch ( m_oRandom.Next( 10 ) )
				{
					// each power is constructed with parameters that control the power up type
					case 0: return new BallSpeedPowerUp( new Vector2( 0.0f , 0.0f ) , Color.Green , 1.25f , "SpeedUpPowerUp" );
					case 1: return new BallSpeedPowerUp( new Vector2( 0.0f , 0.0f ) , Color.Red , 1.0f / 1.25f , "SpeedDownPowerUp" );
					case 2: return new PaddleSizePowerUp( new Vector2( 0.0f , 0.0f ) , Color.Purple , 1.0f / 1.1f , "PaddleSizeDownPowerUp" );
					case 3: return new PaddleSizePowerUp( new Vector2( 0.0f , 0.0f ) , Color.Yellow , 1.1f , "PaddleSizeUpPowerUp" );
					case 4: return new BallSizePowerUp( new Vector2( 0.0f , 0.0f ) , Color.Blue , 1.25f , "BallSizeUpPowerUp" );
					case 5: return new BallSizePowerUp( new Vector2( 0.0f , 0.0f ) , Color.Orange , 1.0f / 1.25f , "BallSizeDownPowerUp" );
					case 6: return new MachineGunPowerUp( new Vector2( 0.0f , 0.0f ) , Color.LightPink , "MachineGunPowerUp" );
					case 7: return new ShotgunPowerUp( new Vector2( 0.0f , 0.0f ) , Color.LightSeaGreen , "ShotgunPowerUp" );
					case 8: return new RiflePowerUp( new Vector2( 0.0f , 0.0f ) , Color.LightSkyBlue , "RiflePowerUp" );
					case 9: return new SplitBallPowerUp( new Vector2( 0.0f , 0.0f ) , Color.Gray , "SplitBallPowerUp" );
				}
			}
			return null;
		}

		// cleans up all entities
		private void ClearAll()
		{
			m_oWalls.Clear();
			m_oBalls.Clear();
			m_oBreakables.Clear();
			m_oPaddles.Clear();
			m_oParticleSystems.Clear();
			m_oPowerUps.Clear();
			m_oVoids.Clear();
			m_oProjectiles.Clear();
		}

		// handles the Direct3D device's reset event by creating device resources
		public void OnResetDirect3DDevice( Direct3D.Device oDevice )
		{
			// register each entity type's vertex buffer
			m_oVertexBuffers[ "Ball" ] = Ball.CreateVB( oDevice );
			m_oVertexBuffers[ "Breakable" ] = Breakable.CreateVB( oDevice );
			m_oVertexBuffers[ "Wall" ] = Wall.CreateVB( oDevice );
			m_oVertexBuffers[ "Paddle" ] = Paddle.CreateVB( oDevice );
			m_oVertexBuffers[ "ParticleSystem" ] = ParticleSystem.CreateVB( oDevice );
			m_oVertexBuffers[ "PowerUp" ] = PowerUp.CreateVB( oDevice );
			m_oVertexBuffers[ "Projectile" ] = Projectile.CreateVB( oDevice );
			m_oVertexBuffers[ "Void" ] = Void.CreateVB( oDevice );

			// load the textures and associate them with their names
			m_oTextures[ "Projectile" ] = TextureLoader.FromFile( oDevice , "Projectile.tga" );
			m_oTextures[ "Paddle" ] = TextureLoader.FromFile( oDevice , "Paddle.tga" );
			m_oTextures[ "Breakable" ] = TextureLoader.FromFile( oDevice , "Breakable.tga" );
			m_oTextures[ "Ball" ] = TextureLoader.FromFile( oDevice , "Ball.tga" );
			m_oTextures[ "PowerUp" ] = TextureLoader.FromFile( oDevice , "PowerUp.tga" );
			m_oTextures[ "ParticleSystem" ] = TextureLoader.FromFile( oDevice , "ParticleSystem.tga" );
			m_oTextures[ "Wall" ] = TextureLoader.FromFile( oDevice , "Wall.tga" );
			m_oTextures[ "Void" ] = TextureLoader.FromFile( oDevice , "Void.tga" );
			m_oTextures[ "SpeedUpPowerUp" ] = TextureLoader.FromFile( oDevice , "SpeedUpPowerUp.tga" );
			m_oTextures[ "SpeedDownPowerUp" ] = TextureLoader.FromFile( oDevice , "SpeedDownPowerUp.tga" );
			m_oTextures[ "PaddleSizeUpPowerUp" ] = TextureLoader.FromFile( oDevice , "PaddleSizeUpPowerUp.tga" );
			m_oTextures[ "PaddleSizeDownPowerUp" ] = TextureLoader.FromFile( oDevice , "PaddleSizeDownPowerUp.tga" );
			m_oTextures[ "BallSizeUpPowerUp" ] = TextureLoader.FromFile( oDevice , "BallSizeUpPowerUp.tga" );
			m_oTextures[ "BallSizeDownPowerUp" ] = TextureLoader.FromFile( oDevice , "BallSizeDownPowerUp.tga" );
			m_oTextures[ "MachineGunPowerUp" ] = TextureLoader.FromFile( oDevice , "MachineGunPowerUp.tga" );
			m_oTextures[ "ShotgunPowerUp" ] = TextureLoader.FromFile( oDevice , "ShotgunPowerUp.tga" );
			m_oTextures[ "RiflePowerUp" ] = TextureLoader.FromFile( oDevice , "RiflePowerUp.tga" );
			m_oTextures[ "SplitBallPowerUp" ] = TextureLoader.FromFile( oDevice , "SplitBallPowerUp.tga" );
		}

		// handles the Direct3D device's lost event by disposing of device resources
		public void OnLostDirect3DDevice()
		{
			// loop over the vertex buffers
			foreach ( DictionaryEntry oEntry in m_oVertexBuffers )
			{
				if ( oEntry.Value != null )
				{
					// dispose of the vertex buffer
					( ( VertexBuffer )oEntry.Value ).Dispose();
				}
			}
			// clear out the vertex buffers
			m_oVertexBuffers.Clear();

			// loop over the textures
			foreach ( DictionaryEntry oEntry in m_oTextures )
			{
				if ( oEntry.Value != null )
				{
					// dispose of the texture
					( ( Texture )oEntry.Value ).Dispose();
				}
			}
			// clear out the textures
			m_oTextures.Clear();
		}

		// renders the game
		public void RenderDisplay( Direct3D.Device oDevice )
		{
			// set device state
			oDevice.RenderState.ZBufferEnable = true;
			// render each of the entity containers
			RenderContainer( oDevice , m_oParticleSystems );
			RenderContainer( oDevice , m_oProjectiles );
			RenderContainer( oDevice , m_oBalls );
			RenderContainer( oDevice , m_oWalls );
			RenderContainer( oDevice , m_oBreakables );
			RenderContainer( oDevice , m_oPaddles );
			RenderContainer( oDevice , m_oPowerUps );
			RenderContainer( oDevice , m_oVoids );
		}

		// renders an entity container
		private void RenderContainer( Direct3D.Device oDevice , ArrayList oEntities )
		{
			for ( int i = 0 ; i < oEntities.Count ; ++i )
			{
				// pull the entity out and render it
				( ( Entity )oEntities[ i ] ).Render( oDevice , this );
			}
		}

		// gets a vertex buffer associated with the provided name
		public VertexBuffer GetVB( string oName )
		{
			return ( VertexBuffer )m_oVertexBuffers[ oName ];
		}

		// gets a texture associated with the provided name
		public Texture GetTexture( string oName )
		{
			return ( Texture )m_oTextures[ oName ];
		}

		// attempts to play a sounds effect with the given name
		public void PlaySound( string oSoundName )
		{
			// check to see if the name has an associated array
			if ( m_oSounds[ oSoundName ] != null )
			{
				// extract the array
				object[] oBuffers = ( object[] )m_oSounds[ oSoundName ];
				// loop over each sound buffer in the array
				for ( int i = 0 ; i < oBuffers.Length ; ++i )
				{
					SecondaryBuffer oBuffer = ( SecondaryBuffer )oBuffers[ i ];
					// check to see if the buffer is being played already
					if ( !oBuffer.Status.Playing )
					{
						// found one that is not being played so play it
						oBuffer.Play( 0 , BufferPlayFlags.Default );
						return;
					}
				}
				// if no sound was found that was not playing the sound gets ignored
			}
		}

		// update the game with the mouse input
		public void UpdateInput( MouseState oMouseState )
		{
			// there could be multiple paddles so loop over them
			for ( int i = 0 ; i < m_oPaddles.Count ; ++i )
			{
				// let the paddle handle the mouse movement input
				( ( Paddle )m_oPaddles[ i ] ).MouseMoveEvent( oMouseState.X , oMouseState.Y );
			}
			// check for button states
			if ( ( oMouseState.GetMouseButtons()[ 0 ] & 128 ) == 128 )
			{
				// loop over the paddles
				foreach ( Paddle oPaddle in m_oPaddles )
				{
					// let the paddle handle the button event
					oPaddle.MouseLeftDownEvent( this );
				}
			}
			else
			{
				// loop over the paddles
				foreach ( Paddle oPaddle in m_oPaddles )
				{
					// let the paddle handle the button event
					oPaddle.MouseLeftUpEvent( this );
				}
			}
		}

		// updates all the game entites
		public void UpdateGame( float fDeltaTime )
		{
			// update each entity container
			UpdateContainer( fDeltaTime , m_oWalls );
			UpdateContainer( fDeltaTime , m_oBalls );
			UpdateContainer( fDeltaTime , m_oBreakables );
			UpdateContainer( fDeltaTime , m_oPaddles );
			UpdateContainer( fDeltaTime , m_oParticleSystems );
			UpdateContainer( fDeltaTime , m_oPowerUps );
			UpdateContainer( fDeltaTime , m_oVoids );
			UpdateContainer( fDeltaTime , m_oProjectiles );

			// once moved, check for collisions
			DetectCollisions();
			// resolve the collisions
			ResolveCollisions();

			// handle game events

			// check if there is still a ball left in the game, including one that might be owned by the paddle
			bool bBall = false;
			foreach( Paddle oPaddle in m_oPaddles )
			{
				if ( oPaddle.Ball != null )
				{
					bBall = true;
				}
			}
			if ( ( m_oBalls.Count <= 0 ) && !bBall )
			{
				// if no balls left in the game
				// check for extra balls
				if ( m_iPlayerBalls == 0 )
				{
					// if the player has no extra balls
					// then end the game
					m_bGameOver = true;
				}
				else
				{
					// player has extra balls then use one
					m_iPlayerBalls--;
					m_oPowerUps.Clear();
					m_oProjectiles.Clear();
					foreach( Paddle oPaddle in m_oPaddles )
					{
						if ( oPaddle.Ball == null )
						{
							oPaddle.Ball = new Ball( this , ( ( Line )oPaddle.GetBounds() ).Center + new Vector2( 0.0f , 0.04f ) , new Vector2( 0.0f , m_fInitialBallSpeed ) , Vector2.Empty );
						}
					}
				}
			}
			if ( m_oBreakables.Count == 0 )
			{
				// if no breakable left in the game
				// then proceed to the next level

				// clear all entities
				ClearAll();
				// increase the difficulty as levels are finished
				m_fInitialBallSpeed += m_fInitialBallSpeedIncrement;
				m_iCurrentLevel = ( m_iCurrentLevel + 1 ) % m_iLevelCount;
				m_fGrowthRate += m_fGrowthIncrement;
				// load the next level's entities
				LoadLevel();
			}
		}

		// resets the game by setting initial difficulty vars and loading the first level
		public void Reset()
		{
			m_bGameOver = false;
			m_iCurrentLevel = 0;
			m_iPlayerBalls = 5;
			m_iPlayerScore = 0;
			m_fGrowthRate = 0.001f;
			m_fInitialBallSpeed = 1.0f;
			ClearAll();
			LoadLevel();
		}

		// updates the entities in the container
		private void UpdateContainer( float fDeltaTime , ArrayList oEntities )
		{
			// loop over each entity in the container
			for ( int i = 0 ; i < oEntities.Count ; ++i )
			{
				// allow the entity to update its self
				( ( Entity )oEntities[ i ] ).Update( this , fDeltaTime );
			}
		}

		// detect collisions between entities
		private void DetectCollisions()
		{
			// clear out all previously collected collisions
			m_oCollisions.Clear();
			// walls and balls
			CollideContainers( m_oWalls , m_oBalls );
			// breakables and balls
			CollideContainers( m_oBreakables , m_oBalls );
			// paddles and balls
			CollideContainers( m_oPaddles , m_oBalls );
			// voids and balls
			CollideContainers( m_oVoids , m_oBalls );
			// paddles and power ups
			CollideContainers( m_oPaddles , m_oPowerUps );
			// voids and power ups
			CollideContainers( m_oVoids , m_oPowerUps );
			// projectiles and walls
			CollideContainers( m_oProjectiles , m_oWalls );
			// projectiles and breakables
			CollideContainers( m_oProjectiles , m_oBreakables );
		}

		// resolves each collision currently in the game
		private void ResolveCollisions()
		{
			// loop over the collected collisions
			foreach ( DictionaryEntry oEntry in m_oCollisions )
			{
				// let the entity handle the collision
				Entity oEntity = ( Entity )oEntry.Key;
				oEntity.Collide( this , ( CollideArgs )oEntry.Value );
			}
		}

		// detect collisions between each entity in the containers
		private void CollideContainers( ArrayList oContainer1 , ArrayList oContainer2 )
		{
			foreach ( Entity oEntity1 in oContainer1 )
			{
				foreach ( Entity oEntity2 in oContainer2 )
				{
					// detect collisions between this pair of entites
					CollideEntities( oEntity1 , oEntity2 );
				}
			}
		}

		// detect collisions between the two provided entities
		private void CollideEntities( Entity oEntity1 , Entity oEntity2 )
		{
			// get the entity bounds, figure out the bounds 
			// types and call the correct collision function for the types
			if ( oEntity1.GetBounds() is Line )
			{
				if ( oEntity2.GetBounds() is Circle )
				{
					CollideLineCircle( oEntity1 , oEntity2 );
				}
			}
			else if ( oEntity1.GetBounds() is Circle )
			{
				if ( oEntity2.GetBounds() is Line )
				{
					CollideLineCircle( oEntity2 ,  oEntity1 );
				}
				else if ( oEntity2.GetBounds() is Circle )
				{
					CollideCircles( oEntity1 , oEntity2 );
				}
			}
		}

		// detects collision between two circles
		private void CollideCircles( Entity oEntity1 , Entity oEntity2 )
		{
			Circle oCircle1 = ( Circle )oEntity1.GetBounds();
			Circle oCircle2 = ( Circle )oEntity2.GetBounds();
			Vector2 oResult = Vector2.Subtract( oCircle2.Location , oCircle1.Location );
			if ( oResult.Length() <= ( oCircle1.Radius + oCircle2.Radius ) )
			{
				oResult.Normalize();
				CollideArgs oArgs = null;
				if ( m_oCollisions[ oEntity1 ] == null )
				{
					oArgs = new CollideArgs();
					m_oCollisions[ oEntity1 ] = oArgs;
				}
				else
				{
					oArgs = ( CollideArgs )m_oCollisions[ oEntity1 ];
				}
				oArgs.AddCollider( oEntity2 , -oResult );
				if ( m_oCollisions[ oEntity2 ] == null )
				{
					oArgs = new CollideArgs();
					m_oCollisions[ oEntity2 ] = oArgs;
				}
				else
				{
					oArgs = ( CollideArgs )m_oCollisions[ oEntity2 ];
				}
				oArgs.AddCollider( oEntity1 , oResult );
			}
		}

		// detects collision between a line and a circle
		private void CollideLineCircle( Entity oEntity1 , Entity oEntity2 )
		{
			Line oLine = ( Line )oEntity1.GetBounds();
			Circle oCircle = ( Circle )oEntity2.GetBounds();

			//Find a point on the line that is closest to the circle
			Vector2 Y = Vector2.Subtract( oCircle.Location , oLine.StartPoint );
			Vector2 U = Vector2.Subtract( oLine.EndPoint , oLine.StartPoint );
			//fT: Line segment parameter (0 to 1)
			float fT = Vector2.Dot( Y , U ) / Vector2.Dot( U , U );
			Vector2 oYHat = Vector2.Multiply( U , fT );
			Vector2 oD = Vector2.Subtract( Y , oYHat );
			float fD = oD.Length();

			//circle.point is outside of the line segment, but close to the startPoint side
			Vector2 oResult;  //shortest line from circle to line
			if ( fT < 0.0f )
			{
				oResult = oCircle.Location - oLine.StartPoint;
			}
				//circle.point is outside of the line segment, but close to the endPoint side
			else if ( fT > 1.0f ) 
			{
				oResult = oCircle.Location - oLine.EndPoint;
			}
				//circle.point is within the segment
			else
			{
				oResult = oD;
			}
			if ( oResult.Length() <= oCircle.Radius )
			{
				oResult.Normalize();
				CollideArgs oArgs = null;
				if ( m_oCollisions[ oEntity2 ] == null )
				{
					oArgs = new CollideArgs();
					m_oCollisions[ oEntity2 ] = oArgs;
				}
				else
				{
					oArgs = ( CollideArgs )m_oCollisions[ oEntity2 ];
				}

				if ( ( oEntity1 is Paddle ) && ( oEntity2 is Ball ) )
				{
					Vector2 oNormal = ( ( Circle )( ( Ball )oEntity2 ).GetBounds() ).Location - ( ( Paddle )oEntity1 ).SpecialPoint;
					oArgs.AddCollider( oEntity1 , oNormal );
				}
				else
				{
					oArgs.AddCollider( oEntity1 , oResult );
				}

				if ( m_oCollisions[ oEntity1 ] == null )
				{
					oArgs = new CollideArgs();
					m_oCollisions[ oEntity1 ] = oArgs;
				}
				else
				{
					oArgs = ( CollideArgs )m_oCollisions[ oEntity1 ];
				}
				oArgs.AddCollider( oEntity2 , -oResult );
			}
		}
	}
}
