C# - OOP Buttons Guessing Game


Overview

This is a graphical guesstimate game. Each new game a random number of yellow 'Buttons' are drawn on the game grid. The red vertical partition line is also drawn on a random vertical grid line. This partition line isn't moveable. Mouse movement over the blue selection triangles (to the right of the grid) causes a black horizontal partion line to be drawn on the selected horizontal grid line. When mouse hovering over one of the rightmost triangles, some of the yellow 'Buttons' will become black 'Buttons' and some will become red 'Buttons'. Clicking on one of the rightmost triangles selects the current amounts of red 'Buttons' and black 'Buttons'. The aim of the game is to estimate which of the triangles you must click on to select more black 'Buttons' than red 'Buttons'. The real skill in the game is not only to win, but also to win by the narrowest margin possible...

This is an application developed using standard OOP techniques, namely encapsulation and custom events. The game core is entirely encapsulated within an extended Panel, where the GDI+ graphical elements are rendered on to the panel. Standard Panel events are used to detect mouse movement and clicks. Two custom events send feedback to the Form when the game is ended, one to enable the New Game button, and the other to provide the result String which is then displayed in a Label.


The GamePanel Class

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
 
namespace Buttons_cs
{
    class GamePanel : Panel
    {
        //custom events
        public event  EventHandler enableNewButton;
        public event  EventHandler<String> showScoreLine;
 
        //graphical elements, stored as GraphicsPaths 
        //for use with mouse movement And clicks
        private List<GraphicsPath> topTriangles = new List<GraphicsPath>();
        private List<GraphicsPath> rightTriangles = new List<GraphicsPath>();
 
        //game play variables
        private int  topIndex = -1;
        private int  rightIndex = -1;
        private int  oldRightIndex = -1;
 
        //used for storing locations of random graphical buttons
        private List<Point> buttons = new List<Point>();
        private Random r = new Random();
 
        private bool  locked = false;
 
        public GamePanel(){
            this.Size = new  Size(570, 570);
            this.DoubleBuffered = true;
            newGame();
        }
 
        /// <summary>
        /// Randomly places a random amount of buttons in random positions
        /// Randomly positions the vertical divider line
        /// </summary>
        public void  newGame()
        {
            rightIndex = -1;
            locked = false;
 
            buttons = new  List<Point>();
            int n = r.Next(500, 1001);
            for (int c = 1; c <= n; c++)
            {
                int x = r.Next(0, 37);
                int y = r.Next(0, 37);
                if (!buttons.Contains(new Point(x, y)))
                {
                    buttons.Add(new Point(x, y));
                }
                else
                {
                    c -= 1;
                }
            }
 
            int[] buttonCounts = Enumerable.Range(15, 7).Select((x) => buttons.Where((p) => p.X < x && p.Y < 17).Count() + buttons.Where((p) => p.X > x && p.Y > 17).Count()).ToArray();
            topIndex = 15 + Array.IndexOf(buttonCounts, buttonCounts.Max());
 
            this.Invalidate();
        }
 
        /// <summary>
        /// Detects clicking on the triangles to the right of the grid
        /// Locks the game and calls the Paint event.
        /// </summary>
        /// <param name="e"></param>
        protected override  void OnMouseDown(System.Windows.Forms.MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                if (rightTriangles.FindIndex((gp) => gp.IsVisible(e.X, e.Y)) != -1)
                {
                    locked = true;
                    enableNewButton?.Invoke(this, new  EventArgs());
                    this.Cursor = Cursors.Default;
                    this.Invalidate();
                }
            }
            base.OnMouseDown(e);
        }
 
        /// <summary>
        /// Detects movement on the triangles to the right of the grid
        /// causing different amounts of buttons to be rendered red and black
        /// depending on their position relative to the partition lines
        /// </summary>
        /// <param name="e"></param>
        protected override  void OnMouseMove(System.Windows.Forms.MouseEventArgs e)
        {
            //topIndex = topTriangles.FindIndex(Function(gp) gp.IsVisible(e.X, e.Y))
            if (!locked)
            {
                rightIndex = rightTriangles.FindIndex((gp) => gp.IsVisible(e.X, e.Y));
                if (rightIndex != -1)
                {
                    this.Cursor = Cursors.Hand;
                }
                else
                {
                    this.Cursor = Cursors.Default;
                }
                if (rightIndex != oldRightIndex)
                {
                    this.Invalidate();
                }
            }
            base.OnMouseMove(e);
        }
 
        /// <summary>
        /// The paint event dynamically renders the panel
        /// drawing both the fixed elements and the dynamic user defined parts
        /// </summary>
        /// <param name="e"></param>
        protected override  void OnPaint(System.Windows.Forms.PaintEventArgs e)
        {
            //the grid is drawn in these two for, next loops
            for (int x = 15; x <= 555; x += 15)
            {
                e.Graphics.DrawLine(Pens.LightGray, x, 15, x, 555);
            }
            for (int y = 15; y <= 555; y += 15)
            {
                e.Graphics.DrawLine(Pens.LightGray, 15, y, 555, y);
            }
            //the buttons are drawn on the grid in one of the three colours
            //and documented as they are drawn
            string[] buttonCounts = new  string[buttons.Count];
            for (int x = 0; x < buttons.Count; x++)
            {
                e.Graphics.FillEllipse(((rightIndex == -1) ? Brushes.Yellow : (((buttons[x].X < (topIndex + 1) && buttons[x].Y < (rightIndex + 1)) || (buttons[x].X > (topIndex + 1) && buttons[x].Y > (rightIndex + 1))) ? Brushes.Red : (((buttons[x].X > (topIndex + 1) && buttons[x].Y < (rightIndex + 1)) || (buttons[x].X < (topIndex + 1) && buttons[x].Y > (rightIndex + 1))) ? Brushes.Black : Brushes.Yellow))), new  Rectangle(((buttons[x].X + 1) * 15) - 5, ((buttons[x].Y + 1) * 15) - 5, 10, 10));
                buttonCounts[x] = (rightIndex == -1) ? "g" : (((buttons[x].X < (topIndex + 1) && buttons[x].Y < (rightIndex + 1)) || (buttons[x].X > (topIndex + 1) && buttons[x].Y > (rightIndex + 1))) ? "r" : (((buttons[x].X > (topIndex + 1) && buttons[x].Y < (rightIndex + 1)) || (buttons[x].X < (topIndex + 1) && buttons[x].Y > (rightIndex + 1))) ? "b" : "g"));
            }
            //these two for, next loops draws the triangles above and to the right of the grid
            Point[] triangle1 =
            {
                new Point(0, 0),
                new Point(15, 0),
                new Point(8, 10),
                new Point(0, 0)
            };
            for (int x = 30; x <= 540; x += 15)
            {
                e.Graphics.FillPolygon(Brushes.SteelBlue, Array.ConvertAll(triangle1, (p) => new  Point(p.X + x - 8, p.Y + 5)));
                if (topTriangles.Count < 35)
                {
                    GraphicsPath gp = new  GraphicsPath();
                    gp.AddPolygon(Array.ConvertAll(triangle1, (p) => new  Point(p.X + x - 8, p.Y + 5)));
                    topTriangles.Add(gp);
                }
            }
            Point[] triangle2 =
            {
                new Point(0, 8),
                new Point(10, 0),
                new Point(10, 15),
                new Point(0, 8)
            };
            for (int y = 30; y <= 540; y += 15)
            {
                e.Graphics.FillPolygon(Brushes.SteelBlue, Array.ConvertAll(triangle2, (p) => new  Point(p.X + 555, p.Y + y - 8)));
                if (rightTriangles.Count < 35)
                {
                    GraphicsPath gp = new  GraphicsPath();
                    gp.AddPolygon(Array.ConvertAll(triangle2, (p) => new  Point(p.X + 555, p.Y + y - 8)));
                    rightTriangles.Add(gp);
                }
            }
            //this draws the vertical partition line
            if (topIndex != -1)
            {
                int x = Convert.ToInt32(topTriangles[topIndex].GetLastPoint().X);
                e.Graphics.DrawLine(Pens.Red, x, 15, x, 555);
            }
            //this draws the changeable horizontal selecting partition line
            if (rightIndex != -1)
            {
                int y = Convert.ToInt32(rightTriangles[rightIndex].PathPoints[0].Y);
                e.Graphics.DrawLine(Pens.Black, 15, y, 555, y);
                //if game over...
                if (locked)
                {
                    int red = buttonCounts.Count((s) => s == "r");
                    int black = buttonCounts.Count((s) => s == "b");
                    showScoreLine?.Invoke(this, string.Format("Red: {0}, Black: {1}. You " + ((red > black) ? "lose" : ((red == black) ? "draw" : "win")), red, black));
                }
            }
            base.OnPaint(e);
        }
 
    }
}

Conclusion

This is another example that shows C# and GDI+ are a good choice of technologies when writing this sort of desktop game...


Other Resources

VB.Net TechNet version
Download here (VB.NET and C#)


VB.Net - WordSearch
VB.Net - Vertex
VB.Net - Perspective
VB.Net - MasterMind
VB.Net - OOP BlackJack
VB.Net - Numbers Game
VB.Net - HangMan
Console BlackJack - VB.Net | C#
TicTacToe - VB.Net | C#
OOP Sudoku - VB.Net | C#
OctoWords VB.Net | C#
OOP Tangram Shapes Game VB.Net | C#
VB.Net - Three-card Monte
VB.Net - Split Decisions
VB.Net - Pascal's Pyramid
VB.Net - Random Maze Games
(Office) Wordsearch Creator
VB.Net - Event Driven Programming - LockWords Game
C# - Crack the Lock
VB.Net - Totris