www.pudn.com > sudoku.rar > WinningAnimation.cs


//-------------------------------------------------------------------------- 
//  
//  Copyright (c) Microsoft Corporation.  All rights reserved.  
//  
//  File: WinningAnimation.cs 
// 
//  Description: A controls that displays an animation when a puzzle is solved. 
//  
//-------------------------------------------------------------------------- 
 
using System; 
using System.Collections; 
using System.Drawing; 
using System.Drawing.Drawing2D; 
using System.Drawing.Imaging; 
using System.Windows.Forms; 
using Microsoft.Sudoku.Utilities; 
 
namespace Microsoft.Sudoku.Controls 
{ 
	/// End of game animation for the Puzzle Grid. 
	internal class WinningAnimation : Control 
	{ 
		/// Used for randomness in the animation. 
		Random _rand; 
		/// List of sprites to be rendered. 
		ArrayList _sprites; 
		/// Animation timer. 
		Timer _timer; 
		/// Used for centering text in the window. 
		StringFormat _sf; 
		/// PuzzleGrid in which we're contained. 
		PuzzleGrid _grid; 
 
		/// Initializes the animation. 
		/// The grid in which this animation should be displayed. 
		public WinningAnimation(PuzzleGrid grid) : base(grid, string.Empty) 
		{ 
			_rand = new Random(); 
			_grid = grid; 
			SetStyle( 
				ControlStyles.AllPaintingInWmPaint | ControlStyles.DoubleBuffer | ControlStyles.ResizeRedraw |  
				ControlStyles.UserPaint | ControlStyles.SupportsTransparentBackColor, true); 
			BackColor = Color.Transparent; 
			Visible = false; 
		} 
 
		/// An image to render underneath the sprites. 
		private Bitmap _underImage; 
 
		protected override void OnResize(EventArgs e) 
		{ 
			base.OnResize (e); 
			 
			// NOTE: This Bitmap and Region work is done to workaround 
			// the performance issues causes by using BackColor = Color.Transparent, 
			// which would allow the exact functionality we want, but which is very 
			// slow when the window is big or maximized. 
			int width = Width, height = Height; 
			if (width > 0 && height > 0) 
			{ 
				// Update the boundaries of the control so we only draw within our own area 
				int arcSize = (int)(width / 14.5); 
				if (Region != null)  
				{ 
					Region.Dispose(); 
					Region = null; 
				} 
				using(GraphicsPath path = GraphicsHelpers.CreateRoundedRectangle(width, height, arcSize)) 
				{ 
					Region = new Region(path); 
				} 
 
				// Update the background underlying image to be rendered under 
				// the sprites 
				if (_underImage != null) _underImage.Dispose(); 
				_underImage = new Bitmap(width, height, PixelFormat.Format32bppPArgb); 
				using(Graphics g = Graphics.FromImage(_underImage)) 
				{ 
					_grid.DrawToGraphics(g, _grid.ClientRectangle); 
					using(SolidBrush b = new SolidBrush(Color.FromArgb(128, Color.SlateGray))) 
					{ 
						g.FillRectangle(b, ClientRectangle); 
					} 
				}  
			} 
		} 
  
		/// Raises the VisibleChanged event and sets whether the animation is running based on the visibility. 
		protected override void OnVisibleChanged(EventArgs e) 
		{ 
			base.OnVisibleChanged (e); 
			AnimationEnabled = Visible; 
		} 
 
		/// Gets or sets whether the animation is enabled. 
		private bool AnimationEnabled 
		{ 
			get { return _timer != null; } 
			set 
			{ 
				if (value) 
				{ 
					// Reset any state from last time around 
					if (_timer != null) Cleanup(); 
					OnResize(EventArgs.Empty); 
 
					// Sets up string formatting so that the congratulations text is centered 
					BackColor = Color.Black; 
					_sf = new StringFormat(); 
					_sf.Alignment = StringAlignment.Center; 
					_sf.LineAlignment = StringAlignment.Center; 
 
					// Create the list to store the sprites 
					_sprites = new ArrayList(_grid.State.GridSize*_grid.State.GridSize); 
 
					// Setup the animation timer 
					_timer = new Timer(); 
					_timer.Interval = 1000/24; // 24 fps 
					_timer.Tick += new EventHandler(UpdateSpritesOnTimerTick); 
 
					// Initialize all of the sprites 
					using(Bitmap bmp = new Bitmap(_grid.ClientRectangle.Width, _grid.ClientRectangle.Height)) 
					{ 
						// Take a snapshot of the underlying puzzle grid 
						using(Graphics g = Graphics.FromImage(bmp)) 
						{ 
							_grid.DrawToGraphics(g, _grid.ClientRectangle); 
						} 
 
						// Setup each individual sprite based on pulling out a section 
						// of the underlying grid snapshot 
						for(int i=0; i<_grid.State.GridSize; i++) 
						{ 
							for(int j=0; j<_grid.State.GridSize; j++) 
							{ 
								RectangleF cellRect = PuzzleGrid.GetCellRectangle(_grid.BoardRectangle, new Point(i,j)); 
								Bitmap smallImage = new Bitmap((int)cellRect.Width, (int)cellRect.Height, PixelFormat.Format32bppPArgb); 
								using(Graphics g = Graphics.FromImage(smallImage)) 
								{ 
									g.DrawImage(bmp, 0, 0, cellRect, GraphicsUnit.Pixel); 
								} 
								ImageSprite s = new ImageSprite(smallImage); 
 
								// Each sprite is setup with random velocity and angular velocity. 
								// It's position is set to overlap with the underlying puzzle grid. 
								const int maxSpeed = 10; 
								 
								// Set the location to the same location as on the grid 
								Point loc = Point.Truncate(cellRect.Location); 
								s.Location = PointToClient(_grid.PointToScreen(loc)); 
							 
								// Set a random linear velocity 
								do 
								{ 
									s.Velocity=new Size(_rand.Next(maxSpeed*2)-maxSpeed, _rand.Next(maxSpeed*2)-maxSpeed); 
								}  
								while( 
									s.Velocity.Width >= -2 && s.Velocity.Width <= 2 && 
									s.Velocity.Height >= -2 && s.Velocity.Height <= 2); 
								 
								// Set a random angular velocity 
								do 
								{ 
									s.AngularVelocity=(float)_rand.Next(maxSpeed*2) - maxSpeed; 
								}  
								while(s.AngularVelocity == 0); 
								s.RotateAroundCenter = (_rand.Next(2) == 0); 
 
								// Set a random flip speed (really angular velocity but around a different axis) 
								do 
								{ 
									s.FlipSpeed = _rand.Next(maxSpeed*2)-(maxSpeed); 
								} 
								while(s.FlipSpeed == 0); 
								 
								// Add the completed sprite to the list 
								_sprites.Add(s); 
							} 
						} 
					} 
 
					_timer.Start(); 
				} 
				else Cleanup(); 
			} 
		} 
 
		/// Cleans up resources. 
		private void Cleanup() 
		{ 
			if (_timer != null) 
			{ 
				_timer.Stop(); 
				_timer.Dispose(); 
				_timer = null; 
			} 
			if (_underImage != null) 
			{ 
				_underImage.Dispose(); 
				_underImage = null; 
			} 
			if (_sf != null) 
			{ 
				_sf.Dispose(); 
				_sf = null; 
			} 
			if (_sprites != null) 
			{ 
				foreach(IDisposable disposable in _sprites) disposable.Dispose(); 
				_sprites = null; 
			} 
		} 
 
		/// Releases the unmanaged resources used by the Control and optionally releases the managed resources. 
		/// true to release both managed and unmanaged resources; false to release only unmanaged resources. 
		protected override void Dispose( bool disposing ) 
		{ 
			if (disposing) Cleanup(); 
			base.Dispose(disposing); 
		} 
 
		/// Raises the Paint event. 
		/// A PaintEventArgs that contains the event data. 
		protected override void OnPaint(PaintEventArgs pe) 
		{ 
			// Do base painting 
			base.OnPaint(pe); 
 
			// Draw the base underlying image 
			if (_underImage != null) pe.Graphics.DrawImage(_underImage, 0, 0); 
 
			// Render all of the sprites 
			if (_sprites != null && _sprites.Count > 0) 
			{ 
				for(int i=_sprites.Count-1; i>=0; --i) 
				{ 
					ImageSprite s = (ImageSprite)_sprites[i]; 
					s.Paint(pe.Graphics); 
				} 
			} 
 
			// Show the congratulatory text 
			string text = ResourceHelper.PuzzleSolvedCongratulations; 
			if (_sf != null && text != null && text.Length > 0) 
			{ 
				float emSize = GraphicsHelpers.GetMaximumEMSize(text, 
					pe.Graphics, Font.FontFamily, Font.Style, ClientRectangle.Width, ClientRectangle.Height); 
				using(Font f = new Font(Font.FontFamily, emSize)) 
				{ 
					pe.Graphics.DrawString(text, f, Brushes.Black, new RectangleF(2, 2, ClientRectangle.Width, ClientRectangle.Height), _sf); 
					pe.Graphics.DrawString(text, f, Brushes.Gray, new RectangleF(-1, -1, ClientRectangle.Width, ClientRectangle.Height), _sf); 
					pe.Graphics.DrawString(text, f, Brushes.Yellow, new RectangleF(0, 0, ClientRectangle.Width, ClientRectangle.Height), _sf); 
				} 
			} 
		} 
 
		/// Updates the position and orientation of all sprites on each timer tick. 
		/// The timer. 
		/// Event args. 
		private void UpdateSpritesOnTimerTick(object sender, EventArgs e) 
		{ 
			if (_timer != null) 
			{ 
				if (_sprites != null) 
				{ 
					// Update each sprite 
					for(int i=_sprites.Count-1; i>=0; --i) 
					{ 
						// Update the sprite 
						ImageSprite s = (ImageSprite)_sprites[i]; 
						s.Update(); 
 
						// If it's left the visible range, remove it from the list so we don't 
						// need to deal with it any more 
						Rectangle bounds = ClientRectangle; 
						if (s.Location.X > bounds.Right + s.Image.Width || s.Location.X < -s.Image.Width || 
							s.Location.Y > bounds.Bottom + s.Image.Width || s.Location.Y < -s.Image.Width)  
						{ 
							_sprites.RemoveAt(i); 
							s.Dispose(); 
						} 
					} 
				 
					// If there are no sprites left, stop the timer; we're done. 
					if (_sprites.Count == 0) _timer.Stop(); // stop but don't delete, as _timer is used as a marker 
 
					// Refresh the window 
					Invalidate(); 
				} 
			} 
		} 
 
		/// Represents an image sprite to be displayed as part of the animation. 
		internal class ImageSprite : IDisposable 
		{ 
			/// The location of the sprite. 
			Point _location; 
			/// The rotational angle of the sprite. 
			float _angle; 
			/// The current flipped position of the sprite (angular velocity around a different axis). 
			float _flipPosition; 
 
			/// The linear velocity of the sprite. 
			Size _velocity; 
			/// The angular velocity of the sprite. 
			float _angularVelocity; 
			/// The speed at which the sprite flips over itself. 
			float _flipSpeed; 
			/// Whether to rotate around the center or corner. 
			bool _rotateAroundCenter; 
 
			/// The image that is the sprite. 
			private Bitmap _bmp; 
 
			/// Initializes the sprite. 
			/// The sprite's image. 
			public ImageSprite(Bitmap bmp) 
			{ 
				if (bmp == null) throw new ArgumentNullException("bmp"); 
				_bmp = bmp; 
			} 
 
			/// Releases resources used by the sprite. 
			public void Dispose() 
			{ 
				if (_bmp != null) 
				{ 
					_bmp.Dispose(); 
					_bmp = null; 
				} 
			} 
 
			/// Gets the image that is the sprite. 
			public Bitmap Image { get { return _bmp; } } 
			/// Gets or sets the location of the sprite. 
			public Point Location { get { return _location; } set { _location=value; } } 
			/// Gets or sets the flip speed of the sprite. 
			public float FlipSpeed { get { return _flipSpeed; } set { _flipSpeed=value; } } 
			/// Gets or sets the linear velocity of the sprite. 
			public Size Velocity { get { return _velocity; } set { _velocity = value; } } 
			/// Gets or sets the angular velocity of the sprite. 
			public float AngularVelocity { get { return _angularVelocity; } set { _angularVelocity=value; } } 
			/// Sets whether to rotate around the center versus around a corner. 
			public bool RotateAroundCenter { set { _rotateAroundCenter = value; } } 
 
			/// Renders the sprite. 
			/// The graphics object onto which the sprite should be rendered. 
			public void Paint(Graphics graphics) 
			{ 
				// Rotate and translate the graphics object appropriately 
				using(Matrix mx = new Matrix()) 
				{ 
					GraphicsState gs = graphics.Save(); 
					if (_rotateAroundCenter) mx.Translate(-_bmp.Width/2, -_bmp.Height/2, MatrixOrder.Append); // to rotate around the center rather than corner 
					mx.Rotate(_angle, MatrixOrder.Append); 
					if (_rotateAroundCenter) mx.Translate(_bmp.Width/2, _bmp.Height/2, MatrixOrder.Append); // to rotate around the center rather than corner 
					mx.Translate(_location.X, _location.Y, MatrixOrder.Append); 
					graphics.Transform = mx; 
 
					// Draw the image 
					float flipMult = ((float)Math.Cos(_flipPosition*Math.PI/180.0)); 
					if (flipMult > 0.001 || flipMult < -0.001) // avoids an OOM exception from GDI+ 
					{ 
						graphics.DrawImage(_bmp,  
							new RectangleF(0, 1-Math.Abs(flipMult), _bmp.Width, _bmp.Height*flipMult),  
							new RectangleF(0, 0, _bmp.Width, _bmp.Height),  
							GraphicsUnit.Pixel); 
					} 
			 
					// Restore the graphics object 
					graphics.Restore(gs); 
				} 
			} 
 
			/// Updates the position and orientation of the sprite. 
			public void Update() 
			{ 
				// Update the location based on the lineary velocity 
				_location = new Point(_location.X + _velocity.Width, _location.Y + _velocity.Height); 
				 
				// Update the flipped position based on the flip speed 
				_flipPosition += _flipSpeed; 
				if (_flipPosition >= 360) _flipPosition -= 360; 
				else if (_flipPosition < 0) _flipPosition += 360; 
 
				// Update the rotational angle based on the rotational velocity 
				_angle += _angularVelocity; 
				if (_angle >= 360) _angle -= 360; 
				else if (_angle < 0) _angle += 360; 
			} 
		} 
	} 
}