Click here to Skip to main content
15,881,413 members
Articles / Multimedia / GDI+

Some Unfriendly, Annoying Balls on the Desktop

Rate me:
Please Sign up or sign in to vote.
4.84/5 (44 votes)
3 Oct 20072 min read 75.3K   873   54  
Some unfriendly, annoying balls on the desktop bouncing around and trying to catch the cursor - a mix of GDI+, transparent forms and bit of AI
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Threading;
using System.Windows.Forms;

namespace Replicator {
	/// <summary>
	/// A single Robot
	/// </summary>
	public partial class Robot : Form {
		#region Classes/Enums

		/// <summary>
		/// Holding the avaible brain states for the robot
		/// </summary>
		public enum eBrain {
			Idle,
			Following,
			Attacking,
			CursorCatched,
			Wait
		}

		#endregion

		#region Properties

		/// <summary>
		/// Current X Position (centered)
		/// </summary>
		public float X {
			get { return m_X; }
			set {
				m_X = value;

				//Invoke is required, as the thread is different
				Invoke(new EventHandler(delegate { Location = new Point((int) m_X - Width/2, Location.Y); }));
			}
		}

		/// <summary>
		/// Current Y Position (bottomed ;P)
		/// </summary>
		public float Y {
			get { return m_Y; }
			set {
				m_Y = value;

				//Invoke is required, as the thread is different
				Invoke(new EventHandler(delegate { Location = new Point(Location.X, (int) m_Y - Height); }));
			}
		}

		/// <summary>
		/// Returns the position as a Point
		/// </summary>
		public Point Pos {
			get { return new Point((int) Math.Round(m_X), (int) Math.Round(m_Y)); }
		}

		/// <summary>
		/// Contains the current brain state
		/// Causes redraw when changed
		/// </summary>
		public eBrain State {
			get { return m_State; }
			set {
				if (value != m_State) {
					m_State = value;
					Redraw();
				}
			}
		}

		#endregion

		private static readonly Random m_Random = new Random();
		private bool m_OnGround = false;
		private eBrain m_State = eBrain.Wait;
		private float m_VelX = 0;
		private float m_VelY = 0;
		private float m_X;
		private float m_Y;

		public Robot() {
			InitializeComponent();
			SetStyle(ControlStyles.UserPaint, true); //We draw it ourself
		}

		public Robot(int x, int y) : this() {
			m_X = x;
			m_Y = y;
			m_VelX = (float) m_Random.NextDouble()*3;
			m_VelY = (float) m_Random.NextDouble()*3;
		}

		/// <summary>
		/// Redraws the form
		/// (Crossthread call)
		/// </summary>
		public void Redraw() {
			Invoke(new EventHandler(delegate { Refresh(); }));
		}

		/// <summary>
		/// Draws the robot
		/// </summary>
		/// <param name="sender">-</param>
		/// <param name="e">-</param>
		private void Robot_Paint(object sender, PaintEventArgs e) {
			Graphics g = e.Graphics;

			g.SmoothingMode = SmoothingMode.None; //Border is drawn without antialiasing
			g.FillEllipse(Brushes.Black, 1, 1, Width - 2, Height - 2); //border
			g.SmoothingMode = SmoothingMode.AntiAlias;

			g.DrawEllipse(Pens.White, 2, 2, Width - 4, Height - 4); //Ring

			Brush middle;
			switch (State) {
				default:
				case eBrain.Idle:
					middle = Brushes.Gray;
					break;
				case eBrain.Following:
					middle = Brushes.Blue;
					break;
				case eBrain.Attacking:
					middle = Brushes.Red;
					break;
				case eBrain.CursorCatched:
					middle = Brushes.White;
					break;
				case eBrain.Wait:
					middle = Brushes.Black;
					break;
			}

			g.FillEllipse(middle, 12, 12, Width - 24, Height - 24); //Middle part (Brain indicator)
		}

		/// <summary>
		/// Called when the user clicks with the mouse on one of the robots
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void Robot_MouseClick(object sender, MouseEventArgs e) {
			if (e.Button == MouseButtons.Right) {
				Environment.Exit(0);
			}

			//Set state to inactive
			State = eBrain.Wait;
			m_VelX = (float) m_Random.NextDouble()*3;
			m_VelY = (float) m_Random.NextDouble()*3;
		}

		/// <summary>
		/// Called when the form is shown
		/// Starts the robot thread
		/// </summary>
		/// <param name="sender">-</param>
		/// <param name="e"-></param>
		private void Robot_Shown(object sender, EventArgs e) {
			X = m_X;
			Y = m_Y;

			//Start our acting thread
			Thread a = new Thread(Loop);
			a.Start();
		}

		/// <summary>
		/// The Mainloop of the robot
		/// Calls Act() and Think()
		/// </summary>
		public void Loop() {
			while (!Disposing) {
				Think();
				Act();
				Thread.Sleep(33);
			}
		}

		/// <summary>
		/// Represents the acting part of the robot
		/// Or in other words: the physics/mech part
		/// </summary>
		public void Act() {
			//Friction..
			m_VelX *= 0.95f;
			m_VelY *= 0.95f;

			//Stop movement complete if its to low, otherwise there'll be some sort of "endless jumping"
			if (Math.Abs(m_VelX) < 0.5f) {
				m_VelX = 0;
			}
			if (Math.Abs(m_VelY) < 0.5f) {
				m_VelY = 0;
			}

			m_OnGround = Y >= Screen.PrimaryScreen.WorkingArea.Height;
			if (m_OnGround) //Is on ground
			{
				if (m_VelY > 0) {
					m_VelY = -m_VelY; //"Bounce"
				}
			}
			else {
				m_VelY += 2f; //Apply Gravity!
			}

			if (X < 0) //Out of screen: left
			{
				m_VelX = Math.Abs(m_VelX);
			}
			else if (X > Screen.PrimaryScreen.WorkingArea.Width) //Out of screen: right
			{
				m_VelX = -Math.Abs(m_VelX);
			}

			X += m_VelX;
			Y += m_VelY;
		}

		/// <summary>
		/// Represents the thinking part of the robot
		/// In this part the robot decides what to do
		/// </summary>
		public void Think() {
			double dist = GetDistance(Cursor.Position, Pos);

			switch (State) {
				case eBrain.Idle: //The idle part decides what to do next
					{
						if (dist <= 30) {
							State = eBrain.CursorCatched;
						}
						else if (dist <= 600) {
							State = eBrain.Attacking;
						}
						else if (dist <= 900) {
							State = eBrain.Following;
						}

						Stand();
					}
					break;
				case eBrain.Attacking: //Jump to the target
					{
						if (dist < 30 || dist > 600) {
							State = eBrain.Idle;
						}
						else {
							Walk(Cursor.Position.X - (int) X);
							Jump(Cursor.Position.Y - (int) Y);
						}
					}
					break;
				case eBrain.Following: //Just walk to the target
					{
						if (dist <= 600 || dist > 900) {
							State = eBrain.Idle;
						}
						else {
							Walk(Math.Min(Cursor.Position.X - (int) X, 20));
						}
					}
					break;
				case eBrain.CursorCatched: //Cought!
					{
						//We use the average to give the user the change to move the cursor
						int avgX = (Cursor.Position.X + (int) X) / 2;
						int avgY = (Cursor.Position.Y + (int) Y) / 2;

						Cursor.Position = new Point(avgX, avgY);
						X = avgX;
						Y = avgY;

						if (m_Random.NextDouble() < 0.001) {
							State = eBrain.Wait; //drop of, hanging around there long enough
						}
					}
					break;
				case eBrain.Wait: //Inactive
					{
						if (m_Random.NextDouble() < 0.010) {
							State = eBrain.Idle; //Reactivate
						}
					}
					break;
			}
		}

		#region Robot functions

		/// <summary>
		/// Makes the robot slow down and stand still
		/// </summary>
		public void Stand() {
			Walk(-(int) Math.Round(m_VelX*10));
		}

		/// <summary>
		/// Makes the robot walk
		/// </summary>
		/// <param name="x">Units to walk</param>
		public void Walk(int x) {
			m_VelX += x/100f;
		}

		/// <summary>
		/// Makes the robot jump
		/// </summary>
		/// <param name="y">Height</param>
		public void Jump(int y) {
			if (m_OnGround && y < 0) {
				m_VelY += y/10;
			}
		}

		#endregion

		#region Helpers

		/// <summary>
		/// Returns the distance between two Points
		/// </summary>
		/// <param name="a">Point A</param>
		/// <param name="b">Point B</param>
		/// <returns>Distance</returns>
		private static double GetDistance(Point a, Point b) {
			int relX = a.X - b.X;
			int relY = a.Y - b.Y;
			return Math.Sqrt((relX*relX) + (relY*relY));
		}

		#endregion
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here



Comments and Discussions