Day 4 - Selecting Gems

Objectives

  • Make Gems clickable
  • Selected Gems (by clicking them) have a white border
  • Only one Gem can be selected at one given time

Screenshot

screenshot_2.jpeg

Making Gems Clickable

To make Gems clickable, I created a general interface that has to be implemented by any object that is clickable:

IClickable.cs

using System;
using System.Drawing;
using SdlDotNet.Input;
 
namespace Bejeweled
{
    interface IClickable
    {
        bool IsClickPoint(Point p);
 
        void OnMouseDown(MouseButtonEventArgs e);
        void OnMouseUp(MouseButtonEventArgs e);
        void OnMouseClick(MouseButtonEventArgs e);
 
        event EventHandler<MouseButtonEventArgs> MouseDown;
        event EventHandler<MouseButtonEventArgs> MouseUp;
        event EventHandler<MouseButtonEventArgs> MouseClick;
    }
}

The IsClickPoint method makes it possible for the class consuming this interface (in our case the Canvas) to know wether the mouse is over it or not. The OnMouseXXX methods can then be called to inform the clickable object of that situation. The MouseXXX events will be fired accordingly.

As Gems should be clickable, we make the Gem class implement the IClickable interface:

Gem.cs

using SdlDotNet.Input;
using SdlDotNet.Graphics;
using SdlDotNet.Graphics.Primitives;
using System.Drawing;
 
namespace Bejeweled
{
    class Gem : IDrawable, IClickable
    {
        public Point Position { get; set; }
        public Color Color { get; set; }
        public bool IsSelected { get; set; }
        private int Radius { get; set; }
 
        public Gem()
        {
            Radius = 10;
        }
 
        public void Draw(Surface surface)
        {
            if (surface != null)
            {
                if(IsSelected)
                    surface.Draw(new Ellipse(Position, new Size(Radius+2, Radius+2)), Color.White, true, true);
                surface.Draw(new Ellipse(Position, new Size(Radius, Radius)), Color, true, true);
            }
        }
 
        #region IClickable Members
 
        public bool IsClickPoint(Point p)
        {
            var r = new Rectangle(new Point(Position.X-Radius,Position.Y-Radius),new Size(Radius*2,Radius*2));
            return r.Contains(p);
        }
 
        public void OnMouseDown(MouseButtonEventArgs e)
        {
            if (MouseDown != null)
                MouseDown(this, e);
        }
 
        public void OnMouseUp(MouseButtonEventArgs e)
        {
            if (MouseUp != null)
                MouseUp(this, e);
        }
 
        public void OnMouseClick(MouseButtonEventArgs e)
        {
            if (MouseClick != null)
                MouseClick(this, e);
        }
 
        public event System.EventHandler<MouseButtonEventArgs> MouseDown;
        public event System.EventHandler<MouseButtonEventArgs> MouseUp;
        public event System.EventHandler<MouseButtonEventArgs> MouseClick;
 
        #endregion
    }
}

As you can see, the IsClickPoint implementation checks if the given click point is within the bounding rectangle of the Gem. The OnMouseXXX implementations just throws their according events. Later we could only fire those events when some conditions are met.

Also, a boolean Property IsSelected is added to the Gem class, to be able to draw a white border around the Gem based on that information. Drawing a border is done by drawing a bigger circle with the bordercolor first, and then the Gem itself.

Because the Canvas contains the items that are drawn on the screen, I find it the most logical place (for now) to handle the mouse events and pass them through to the items that are clickable:

Canvas.cs

using System.Collections.Generic;
using System.Drawing;
using SdlDotNet.Core;
using SdlDotNet.Graphics;
 
namespace Bejeweled
{
    class Canvas
    {
        private readonly Surface drawingSurface;
        private readonly List<IDrawable> items;
        private IClickable mouseDownItem;
 
        public Canvas(int width, int height)
        {
            drawingSurface = Video.SetVideoMode(width, height, 32, false, false, false, true);
            items = new List<IDrawable>();
            WireEvents();
        }
 
        private void WireEvents()
        {
            Events.MouseButtonDown += Events_MouseButtonDown;
            Events.MouseButtonUp += Events_MouseButtonUp;
        }
 
        private void Events_MouseButtonUp(object sender, SdlDotNet.Input.MouseButtonEventArgs e)
        {
            IClickable clickable = GetItemUnderMouse(e.Position);
            if(clickable != null)
            {
                clickable.OnMouseUp(e);
                if(mouseDownItem == clickable)
                    clickable.OnMouseClick(e);
                mouseDownItem = null;
            }
        }
 
        private void Events_MouseButtonDown(object sender, SdlDotNet.Input.MouseButtonEventArgs e)
        {
            mouseDownItem = GetItemUnderMouse(e.Position);
            if (mouseDownItem != null)
            {
                mouseDownItem.OnMouseDown(e);
            }
        }
 
        private IClickable GetItemUnderMouse(Point point)
        {
            foreach(IDrawable item in items)
            {
                if(item is IClickable)
                {
                    var clickable = (IClickable) item;
                    if(clickable.IsClickPoint(point))
                        return clickable;
                }
            }
            return null;
        }
 
        public void Add(IDrawable item)
        {
            items.Add(item);
        }
 
        public void Remove(IDrawable item)
        {
            items.Remove(item);
        }
 
        public void Draw()
        {
            drawingSurface.Fill(Color.Black);
            foreach (IDrawable item in items)
            {
                item.Draw(drawingSurface);
            }
            drawingSurface.Update();
        }
    }
}

In the case of a mouse-down, when an item is under the mouse, that item is assigned to the mouseDownItem attribute of the Canvas object and the OnMouseDown method of that clickable item is called. When a mouse-up occurs, and an item is under the mouse, the OnMouseClick method of that clickable item will be called when it is the same item as when the mouse-down event occured. In any case the OnMouseUp method is called. In short, an item is clicked when a mouse-down and mouse-up event occurs on the same item.

To ge the item under the mouse, we loop through all the drawable items in the Canvas and check if the items are clickable. If yes, then the method IsClickPoint of that clickable item is called to check if the mouse is above that item. If yes, that item is returned. If no item is under the mouse, we return null.

Now that everything is in place, we can easily update our Game class to add event handlers to the MouseClick event of our Gems:

Game.cs

using System.Drawing;
using SdlDotNet.Core;
 
namespace Bejeweled
{
    class Game
    {
        private readonly Canvas canvas;
        private Gem selectedGem;
 
        public Game()
        {
            canvas = new Canvas(320,240);
            WireEvents();
            CreateGems();
        }
 
        private void WireEvents()
        {
            Events.Quit += Events_Quit;
            Events.Tick += Events_Tick;
        }
 
        private void CreateGems()
        {
            var gemFactory = new GemFactory();
 
            for(int i=0; i < 10; i++)
            {
                for(int j=0; j < 10; j++)
                {
                    var g = gemFactory.CreateRandom(new Point(16 + i*32, 12 + j*24));
                    g.MouseClick += Gem_MouseClick;
                    canvas.Add(g);
                }
            }
        }
 
        private void Gem_MouseClick(object sender, SdlDotNet.Input.MouseButtonEventArgs e)
        {
            var g = (Gem) sender;
 
            if (selectedGem != null)
                selectedGem.IsSelected = false;
 
            if (g != selectedGem)
            {
                g.IsSelected = true;
                selectedGem = g;
            }
            else
            {
                selectedGem = null;
            }
        }
 
        private static void Events_Quit(object sender, QuitEventArgs e)
        {
            Events.QuitApplication();
        }
 
        private void Events_Tick(object sender, TickEventArgs e)
        {
            canvas.Draw();
        }
 
        public void Play()
        {
            Events.Run();
        }
    }
}
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License