Balloon Game – Delegates & Events

BalloonsCd

BalloonsMainForm

Delegates and events. This was a hard concept for me to grasp when I first starting studying this. Actually let me be more specific. The Delegates part wasn’t that bad. Understanding how Events work, how they’re constructed, triggered, etc… Specifically custom events as you will see from the source. For example double-clicking on the Form_Load event to have Visual Studio create the event for you automatically is easy. But, for example, constructing menu clicks to custom event handlers to custom methods that fire off in a different class was a hard concept to grasp. However once I FINALLY understood all of this everything started falling into place. And I do use what I’ve learned a log in  my other programs. If done right it just makes for cleaner, more understanding code.

Also if you notice I start using the ArrayList collection and you will see other collection methods moving forward as I’m learning about them. Don’t get me wrong Arrays are fun and have their place. But other collection methods can be used also.


MainForm Class


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Ballons
{
    public partial class MainForm : Form
    {
        private BalloonGame _game;              //Balloon game object.
        private static int _currentFrameRate;   //Time control for the frame per second (FPS) variables.
        private static int _lastFrameRate;
        private static int _lastTickCount;
        private bool _gamePaused = false;

        public MainForm()
        {
            InitializeComponent();
        }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private static int getFrameRate()
        {
            //Calculate the frame rate using the elapsed millisecond counter.
            if (System.Environment.TickCount - _lastTickCount >= 1000)
            {
                _lastFrameRate = _currentFrameRate;
                _currentFrameRate = 0;
                _lastTickCount = System.Environment.TickCount;
            }
            _currentFrameRate++;
            return _lastFrameRate;
        }

        private void MainForm_Shown(object sender, EventArgs e)
        {
            //Add the event handlers using a delegate + operator.
            //For the FPS selections.
            fps15ToolStripMenuItem.Click += new EventHandler(gameFPSToolStripMenuItem_Click);
            fps20ToolStripMenuItem.Click += new EventHandler(gameFPSToolStripMenuItem_Click);
            fps30ToolStripMenuItem.Click += new EventHandler(gameFPSToolStripMenuItem_Click);
            fps45ToolStripMenuItem.Click += new EventHandler(gameFPSToolStripMenuItem_Click);
            fps60ToolStripMenuItem.Click += new EventHandler(gameFPSToolStripMenuItem_Click);

            //For the timer interval selections.
            interval5ToolStripItem.Click +=new EventHandler(timerIntervalToolStripMenuItem_Click);
            interval10ToolStripItem.Click += new EventHandler(timerIntervalToolStripMenuItem_Click);
            interval15ToolStripItem.Click += new EventHandler(timerIntervalToolStripMenuItem_Click);
            interval20ToolStripItem.Click += new EventHandler(timerIntervalToolStripMenuItem_Click);
            interval30ToolStripItem.Click += new EventHandler(timerIntervalToolStripMenuItem_Click);
            interval45ToolStripItem.Click += new EventHandler(timerIntervalToolStripMenuItem_Click);
            interval60ToolStripItem.Click += new EventHandler(timerIntervalToolStripMenuItem_Click);

            //For the pause / resume menu item.
            pauseToolStripMenuItem.Click += new EventHandler(pauseToolStripMenuItem_Click);

            //Simulate clicking 15 interval menu item to set timer interval.
            interval15ToolStripItem.PerformClick();

            //Create balloon game object, defaulting the max number of balloons to 10.
            _game = new BalloonGame(10, mainPictureBox.ClientSize);

            //Simulate clicking 20 game FPS menu item for the desired game FPS.
            fps20ToolStripMenuItem.PerformClick();

            //Add an event handler for the balloon info event from the game object using a custom event type.
            _game.OnInfo += new BalloonGame.InfoHandler(OnInfoEventHandler);

            //Add event handler for no balloon info event from the game object using standard no arguments event type.
            _game.OnNoInfo += new EventHandler(OnNoInfoEventHandler);

            //Adding an event handler from the game object update method. DS
            mainPictureBox.Paint += new PaintEventHandler(GameUpdate);

            //Adding an event handler for the game object select method. DS
            mainPictureBox.MouseUp += new MouseEventHandler(GameSelect);

            //Adding a keydown event handler for the game object key pressed method. DS
            this.KeyDown += new KeyEventHandler(KeyPressed);
        }

        //Private method used by the Paint event. DS
        private void GameUpdate(object sender, PaintEventArgs e)
        {
            _game.Update(e.Graphics);
        }

        //private method used by the MouseUp event. DS
        private void GameSelect(object sender, MouseEventArgs e)
        {
            _game.Select(e.Location);
        }

        //Private method used by the KeyDown event. DS
        private void KeyPressed(object sender, KeyEventArgs e)
        {
            //Use these for the location of the balloon. DS
            Console.WriteLine(mainPictureBox.ClientSize.Width);
            Console.WriteLine(_game.XLocation);

            _game.MoveEventHandler(sender, e);
        }

        protected virtual void OnNoInfoEventHandler(object sender, EventArgs e)
        {
            //Respond to the NoInfo event from the game object.
            selectedBalloonToolStripStatusLabel.Text = "No balloon selected";
        }

        protected virtual void OnInfoEventHandler(object sender, BalloonInfoArgs e)
        {
            //Respond to the Info event from the game object.
            string s = "Balloon - ";
            s += "Size: " + e.Info.Dimensions.Width;
            s += "; Lift: " + e.Info.LiftSpeed;
            selectedBalloonToolStripStatusLabel.Text = s;

            //Getting the X and Y coordinates from the selected balloon. DS
            //Then using that info to store in the BallonGame.GetBalloonLocation method. DS
            _game.GetBalloonLocation(e.Info.Location.X, e.Info.Location.Y);
        }

        private void MainForm_Resize(object sender, EventArgs e)
        {
            //Respond to the Form resize.
            _game.BoardSize = mainPictureBox.ClientSize;
        }

        private void gameFPSToolStripMenuItem_Click(object sender, EventArgs e)
        {
            //Adjust FPS rate, checking and unchecking menu items.
            ToolStripMenuItem menuItem = sender as ToolStripMenuItem;
            if (menuItem != null)
            {
                fps15ToolStripMenuItem.Checked = false;
                fps20ToolStripMenuItem.Checked = false;
                fps30ToolStripMenuItem.Checked = false;
                fps45ToolStripMenuItem.Checked = false;
                fps60ToolStripMenuItem.Checked = false;
                menuItem.Checked = true;
                _game.DesiredFrameRate = Convert.ToInt32(menuItem.Text);
            }
        }

        private void timerIntervalToolStripMenuItem_Click(object sender, EventArgs e)
        {
            //Adjust timer interval, checking and unchecking menu items.
            ToolStripMenuItem menuItem = sender as ToolStripMenuItem;
            if (menuItem != null)
            {
                interval5ToolStripItem.Checked = false;
                interval10ToolStripItem.Checked = false;
                interval15ToolStripItem.Checked = false;
                interval20ToolStripItem.Checked = false;
                interval30ToolStripItem.Checked = false;
                interval45ToolStripItem.Checked = false;
                interval60ToolStripItem.Checked = false;
                menuItem.Checked = true;
                gameTimer.Interval = Convert.ToInt32(menuItem.Text);
            }
        }

        //Private method used if the game is paused. DS
        void pauseToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (!_gamePaused)
            {
                pauseToolStripMenuItem.Text = "Resume";     //Changing the menu text to resume. DS
                gameTimer.Enabled = false;                  //Stopping the game timer to pause the graphical movements. DS
                _gamePaused = true;                         //Setting the _gamePaused flag to true. DS
                mainPictureBox.MouseUp -= (GameSelect);     //Removing the mouseup event so the user can't select anything when the game is paused. DS
            }
            else
            {
                pauseToolStripMenuItem.Text = "Pause";      //Changing the meny text back to pause. DS
                gameTimer.Enabled = true;                   //Starting the game timer to continue the graphical movements. DS
                _gamePaused = false;                        //Setting the game paused bool back to false. DS
                mainPictureBox.MouseUp += new MouseEventHandler(GameSelect);        //Re-adding the mouse up event handler so the user can select items. DS
            }
        }

        private void gameTimer_Tick(object sender, EventArgs e)
        {
            //Force the PictureBox evnt to be raised.
            mainPictureBox.Invalidate();

            //Update the status strip labels.
            timerFpsToolStripStatusLabel.Text = "Timer FPS: " + getFrameRate() + " Game FPS: " + _game.DesiredFrameRate;
            balloonCountToolStripStatusLabel.Text = "Balloon count: " + _game.BalloonCount + " of " + _game.MaxBalloons;
        }
    }
}

BalloonGame Class


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Collections;

namespace Ballons
{
    class BalloonGame
    {
        //Public delegates and events.
        public delegate void InfoHandler(object sender, BalloonInfoArgs e);
        public event InfoHandler OnInfo;
        public event EventHandler OnNoInfo;

        //Private class properties.
        private Balloon _activeBalloon = null;
        private ArrayList _balloons = new ArrayList();
        private int _desiredFrameRate = 20;
        private Random _random = new Random();
        private int _lastTickCount;
        private Color _defaultColor = Color.Green;
        private Color _hitColor = Color.Red;
        private int _xLocation;     //Variable used to store the X coordinate location of the balloon. DS
        private int _yLocation;     //Variable used to store the Y coordinate location of the balloon. DS

        //Private class backing variables.
        private Size _boardSize;
        private int _maxBalloons;

        //Public properties.
        public Size BoardSize { set { _boardSize = value; } }
        public int MaxBalloons { get { return _maxBalloons; } }
        public int DesiredFrameRate
        {
            get { return _desiredFrameRate; }
            set { _desiredFrameRate = value; }
        }
        public int XLocation { get { return _xLocation; } }     //Property used to get the X location of the selected balloon. DS

        //Constructor.
        public BalloonGame(int maximumBalloonCount, Size boardSize)
        {
            //Initialize game settings and create the first balloon.
            _boardSize = boardSize;
            _maxBalloons = maximumBalloonCount;
            if (_maxBalloons <= 0)                  _maxBalloons = 5;             _balloons.Add(CreateBalloon());         }         //Private delegates         private Balloon.BalloonDrawAnimateDelegate _balloonDrawAnimate;         //Private methods.         private Balloon CreateBalloon()         {             //Randomly set growth, lift rates, and create the balloon.             int growthRate = _random.Next(10, 41);             int liftRate = _random.Next(1, 6);             Balloon balloon = new Balloon(new Point(_random.Next(_boardSize.Width - 20), _boardSize.Height),                 new Size(20, 20), _defaultColor, growthRate, liftRate);             //Add and event handler to the Popped event for each balloon.             balloon.Popped += new EventHandler(PoppedEventHandler);             //Add balloon drawing and animation method to our delegate list.             _balloonDrawAnimate += balloon.DrawAndAnimate;             return balloon;         }         private void RemoveBalloon(Balloon balloon)         {             //Remove the event delegate handler for the Popped event for this balloon.             balloon.Popped -= this.PoppedEventHandler;             _balloonDrawAnimate -= balloon.DrawAndAnimate;             //Find a balloon in the ArrayList for removal and reset of active balloon.             int index = _balloons.IndexOf(balloon);             if (index >= 0)
            {
                if (_balloons[index] == _activeBalloon) _activeBalloon = null;
                _balloons.RemoveAt(index);

                //Raise the OnNoInfo event.
                OnNoInfo(balloon, EventArgs.Empty);
            }
        }

        //Private event handlers.
        void PoppedEventHandler(object sender, EventArgs e)
        {
            //Remove the popped balloon and add a new balloon, then conditionally add a new balloon
            //if the max balloon count is not reached.
            Balloon balloon = sender as Balloon;
            if (balloon != null) 
                RemoveBalloon(balloon);
                _balloons.Add(CreateBalloon());

            if (_balloons.Count < _maxBalloons)                 _balloons.Add(CreateBalloon());         }         private bool TimeToAnimate()         {             //Determine if the time to animate the game based on desired game frame rate.             bool result = false;             result = ((System.Environment.TickCount - _lastTickCount) >= (1000 / _desiredFrameRate));
            if (result)
                _lastTickCount = System.Environment.TickCount;

            return result;
        }

        //Public methods and event handlers.
        public void Update(Graphics graphics)
        {
            //Check if it's time to animate the objects.
            bool timeToAnimate = TimeToAnimate();

            //Call the delegate to animate and redraw all the current balloons.
            _balloonDrawAnimate(timeToAnimate, _boardSize, graphics);

            //Raise an OnInfo event if we have an active (selected) balloon.
            if (_activeBalloon != null) 
                OnInfo(_activeBalloon, new BalloonInfoArgs(_activeBalloon));
        }

        public void Select(Point location)
        {
            //Loop thru each balloon in the ArrayList to see which one was selected.
            foreach (Balloon balloon in _balloons)
            {
                if (balloon.Hit(location))
                {
                    //Reset the active balloon color.
                    if (_activeBalloon != null && _activeBalloon != balloon)
                        _activeBalloon.FillColor = _defaultColor;
                    _activeBalloon = balloon;
                    _activeBalloon.FillColor = _hitColor;

                    //Raise the OnInfo event with custom arguments.
                    OnInfo(_activeBalloon, new BalloonInfoArgs(_activeBalloon));
                    break;
                }
            }
        }

        //Method used to set the selected ballons X and Y coordinates. DS
        public void GetBalloonLocation(int balloonX, int balloonY)
        {
            _xLocation = balloonX;
            _yLocation = balloonY;
            //Console.WriteLine(_xLocation + ", " + _yLocation);    //Test code. Can remove later. DS
        }

        //The MoveEventHandler Method that moved the balloon depending on which key (left or right) has been pressed. DS
        public void MoveEventHandler(object sender, System.Windows.Forms.KeyEventArgs keyEvent)
        {
            //Console.WriteLine(keyEvent.KeyData);      //Test code, can remove later. DS

            //Checking to make sure there's a balloon selected. DS
            if (_activeBalloon != null)
            {
                //If the left arrow key is pressed. DS
                //if (keyEvent.KeyData == System.Windows.Forms.Keys.Left)
                    //_activeBalloon.MoveLeft(_xLocation);

                //If the right arrow key is pressed. DS
                //if (keyEvent.KeyData == System.Windows.Forms.Keys.Right)
                    //_activeBalloon.MoveRight(_xLocation);

                //Trying a different way of moving the balloons. DS
                _activeBalloon.MoveTheBalloons(_xLocation, keyEvent);
            }
        }

        public int BalloonCount
        {
            get { return (_balloonDrawAnimate != null ? _balloonDrawAnimate.GetInvocationList().Count() : 0); }
        }
    }
}

 

Balloon Class


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing;

namespace Ballons
{
    //Balloon info struct used for custom event arguments.
    public struct BalloonInfo
    {
        public int GrowthRate;
        public int LiftSpeed;
        public int MaxSize;
        public Point Location;
        public Size Dimensions;
        public Color FillColor;
    }

    public class Balloon
    {
        //Private class properties.
        private int _growthCount = 0;
        private int _growthRate;
        private int _maxSize;
        private int _liftSpeed;
        private int _tailLength;
        private Point _location;
        private Size _dimensions;
        private Color _fillColor;

        //Public properties.
        public Color FillColor { set { _fillColor = value; } }

        //Constructor
        public Balloon(Point location, Size dimensions, Color fillColor, int growthRate, int liftSpeed)
        {
            _location = location;
            _dimensions = dimensions;
            _fillColor = fillColor;
            _maxSize = dimensions.Height * 2;
            _growthRate = growthRate;
            _liftSpeed = liftSpeed;
            _tailLength = _dimensions.Height * 2;
        }

        //Public event delegate.
        public event EventHandler Popped;
        public delegate void BalloonDrawAnimateDelegate(bool animate, Size boardSize, Graphics graphics);

        //Public event handler.
        public virtual void OnPopped(EventArgs e)
        {
            if (Popped != null)
                Popped(this, e);
        }

        //Public Methods.
        //Method that takes care of moving the balloon left on the screen. DS
        public void MoveLeft(int xLocation)
        {
            _location.X = xLocation - 1;
        }

        //Method that takes care of moving the balloon right on the screen. DS
        public void MoveRight(int xLocation)
        {
            _location.X = xLocation + 1;
        }

        //Test method trying to combine the two move methods above with the key data. DS
        public void MoveTheBalloons(int xLocation, System.Windows.Forms.KeyEventArgs pressedKey)
        {
            //If the left arrow key is pressed. DS
            if (pressedKey.KeyData == System.Windows.Forms.Keys.Left)
                _location.X--;

            //If the right arrow key is pressed. DS
            if (pressedKey.KeyData == System.Windows.Forms.Keys.Right)
                _location.X++;
        }

        public void DrawAndAnimate(bool animate, Size boardSize, Graphics graphics)
        {
            if (animate)
            {
                //Move and enlarge balloon based on lift and growth rates.
                _location.Y -= _liftSpeed;
                _growthCount++;
                if (_growthCount % _growthRate == 0)
                {
                    _dimensions.Height++;
                    _dimensions.Width++;
                }
            }

            if (_dimensions.Height >= _maxSize)
            {
                //The balloon will pop.
                OnPopped(EventArgs.Empty);
            }
            else
            {
                //Move the balloon.
                if (_location.Y + _dimensions.Height <= 0)
                    _location.Y = boardSize.Height;

                //Draw the balloon and balloon tail.
                using (SolidBrush brush = new SolidBrush(_fillColor))
                    graphics.FillEllipse(brush, new Rectangle(_location, _dimensions));

                Point tailStart = new Point(_location.X + _dimensions.Width / 2, _location.Y + _dimensions.Height);
                Point tailEnd = new Point(tailStart.X, tailStart.Y + _tailLength);
                using (Pen pen = new Pen(_fillColor))
                    graphics.DrawLine(pen, tailStart, tailEnd);
            }
        }

        public BalloonInfo Info()
        {
            return new BalloonInfo()
            {
                GrowthRate = _growthRate,
                LiftSpeed = _liftSpeed,
                MaxSize = _maxSize,
                Location = _location,
                Dimensions = _dimensions,
                FillColor = _fillColor
            };
        }

        public bool Hit(Point location)
        {
            return new Rectangle(_location, _dimensions).Contains(location);
        }
    }
}

 

BalloonInfoArgs EventArgs Class


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Ballons
{
    public class BalloonInfoArgs : EventArgs
    {
        private BalloonInfo _balloonInfo;
        public BalloonInfo Info { get { return _balloonInfo; } }

        public BalloonInfoArgs(Balloon balloon)
        {
            if (balloon != null)
                _balloonInfo = balloon.Info();
        }
    }
}

 

Advertisements

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s