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 partition 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


The line of code - Dim rightTriangles As New List(Of GraphicsPath) defines a List Of GraphicsPath. These are the locations of the triangles to the right of the grid, which are set in the first iteration of the Paint event and used in the Panel MouseMove and Panel MouseDown events...


Imports System.Drawing.Drawing2D
Imports System.Windows.Forms
 
Public Class GamePanel
    Inherits Panel
 
    'custom events
    Public Event enableNewButton()
    Public Event showScoreLine(scoreLine As String)
 
    'graphical elements, stored as GraphicsPaths
    'for use with mouse movement And clicks
    Dim topTriangles As New List(Of GraphicsPath)
    Dim rightTriangles As New List(Of GraphicsPath)
 
    'game play variables
    Dim topIndex As Integer = -1
    Dim rightIndex As Integer = -1
    Dim oldRightIndex As Integer = -1
 
    'used for storing locations of random graphical buttons
    Dim buttons As New List(Of Point)
    Dim r As New Random
 
    Dim locked As Boolean = False
 
    Public Sub New()
        Me.Size = New Size(570, 570)
        Me.DoubleBuffered = True
        newGame()
    End Sub
 
    ''' <summary>
    ''' Randomly places a random amount of buttons in random positions
    ''' Randomly positions the vertical divider line
    ''' </summary>
    Public Sub newGame()
        rightIndex = -1
        locked = False
 
        buttons = New List(Of Point)
        Dim n As Integer = r.Next(500, 1001)
        For c As Integer = 1 To n
            Dim x As Integer = r.Next(0, 37)
            Dim y As Integer = r.Next(0, 37)
            If Not buttons.Contains(New Point(x, y)) Then
                buttons.Add(New Point(x, y))
            Else
                c -= 1
            End If
        Next
 
        Dim buttonCounts() As Integer = Enumerable.Range(15, 7).Select(Function(x) buttons.Where(Function(p) p.X < x And p.Y < 17).Count + buttons.Where(Function(p) p.X > x And p.Y > 17).Count).ToArray
        topIndex = 15 + Array.IndexOf(buttonCounts, buttonCounts.Max)
 
        Me.Invalidate()
    End Sub
 
    ''' <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 Overrides Sub OnMouseDown(ByVal e As System.Windows.Forms.MouseEventArgs)
        If e.Button = MouseButtons.Left Then
            If rightTriangles.FindIndex(Function(gp) gp.IsVisible(e.X, e.Y)) <> -1 Then
                locked = True
                RaiseEvent enableNewButton()
                Me.Cursor = Cursors.Default
                Me.Invalidate()
            End If
        End If
        MyBase.OnMouseDown(e)
    End Sub
 
    ''' <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 Overrides Sub OnMouseMove(ByVal e As System.Windows.Forms.MouseEventArgs)
        'topIndex = topTriangles.FindIndex(Function(gp) gp.IsVisible(e.X, e.Y))
        If Not locked Then
            rightIndex = rightTriangles.FindIndex(Function(gp) gp.IsVisible(e.X, e.Y))
            If rightIndex <> -1 Then
                Me.Cursor = Cursors.Hand
            Else
                Me.Cursor = Cursors.Default
            End If
            If rightIndex <> oldRightIndex Then
                Me.Invalidate()
            End If
        End If
        MyBase.OnMouseMove(e)
    End Sub
 
    ''' <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 Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
        'the grid is drawn in these two for, next loops
        For x As Integer = 15 To 555 Step 15
            e.Graphics.DrawLine(Pens.LightGray, x, 15, x, 555)
        Next
        For y As Integer = 15 To 555 Step 15
            e.Graphics.DrawLine(Pens.LightGray, 15, y, 555, y)
        Next
        'the buttons are drawn on the grid in one of the three colours
        'and documented as they are drawn
        Dim buttonCounts(buttons.Count - 1) As String
        For x As Integer = 0 To buttons.Count - 1
            e.Graphics.FillEllipse(If(rightIndex = -1, Brushes.Yellow, If((buttons(x).X < (topIndex + 1) And buttons(x).Y < (rightIndex + 1)) Or (buttons(x).X > (topIndex + 1) And buttons(x).Y > (rightIndex + 1)), Brushes.Red, If((buttons(x).X > (topIndex + 1) And buttons(x).Y < (rightIndex + 1)) Or (buttons(x).X < (topIndex + 1) And 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) = If(rightIndex = -1, "g", If((buttons(x).X < (topIndex + 1) And buttons(x).Y < (rightIndex + 1)) Or (buttons(x).X > (topIndex + 1) And buttons(x).Y > (rightIndex + 1)), "r", If((buttons(x).X > (topIndex + 1) And buttons(x).Y < (rightIndex + 1)) Or (buttons(x).X < (topIndex + 1) And buttons(x).Y > (rightIndex + 1)), "b", "g")))
        Next
        'these two for, next loops draws the triangles above and to the right of the grid
        Dim triangle1() As Point = {New Point(0, 0), New Point(15, 0), New Point(8, 10), New Point(0, 0)}
        For x As Integer = 30 To 540 Step 15
            e.Graphics.FillPolygon(Brushes.SteelBlue, Array.ConvertAll(triangle1, Function(p) New Point(p.X + x - 8, p.Y + 5)))
            If topTriangles.Count < 35 Then
                Dim gp As New GraphicsPath
                gp.AddPolygon(Array.ConvertAll(triangle1, Function(p) New Point(p.X + x - 8, p.Y + 5)))
                topTriangles.Add(gp)
            End If
        Next
        Dim triangle2() As Point = {New Point(0, 8), New Point(10, 0), New Point(10, 15), New Point(0, 8)}
        For y As Integer = 30 To 540 Step 15
            e.Graphics.FillPolygon(Brushes.SteelBlue, Array.ConvertAll(triangle2, Function(p) New Point(p.X + 555, p.Y + y - 8)))
            If rightTriangles.Count < 35 Then
                Dim gp As New GraphicsPath
                gp.AddPolygon(Array.ConvertAll(triangle2, Function(p) New Point(p.X + 555, p.Y + y - 8)))
                rightTriangles.Add(gp)
            End If
        Next
        'this draws the vertical partition line
        If topIndex <> -1 Then
            Dim x As Integer = CInt(topTriangles(topIndex).GetLastPoint.X)
            e.Graphics.DrawLine(Pens.Red, x, 15, x, 555)
        End If
        'this draws the changeable horizontal selecting partition line
        If rightIndex <> -1 Then
            Dim y As Integer = CInt(rightTriangles(rightIndex).PathPoints(0).Y)
            e.Graphics.DrawLine(Pens.Black, 15, y, 555, y)
            'if game over...
            If locked Then
                Dim red As Integer = buttonCounts.Count(Function(s) s = "r")
                Dim black As Integer = buttonCounts.Count(Function(s) s = "b")
                RaiseEvent showScoreLine(String.Format("Red: {0}, Black: {1}. You " & If(red > black, "lose", If(red = black, "draw", "win")), red, black))
            End If
        End If
        MyBase.OnPaint(e)
    End Sub
 
End Class




Conclusion





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



Other Resources



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




Articles related to game programming



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