Creating a Snake Game in Visual Basic

Because the old classic Nokia phones have made a comeback, I thought it apt to write about the most popular feature of the Nokia-phone–era: Snake. Contrary to popular belief, Nokia did not pioneer the Snake game, though.

Snake is simply a name for a video game concept where the player has to maneuver a line which grows in length. The Snake concept originated in the arcade game Blockade in 1976. Today, however, there are hundreds of variances of the original Snake game.

Playing the game involves a player having to attempt to “eat” items by running into them with the head of the snake. Each item eaten makes the snake longer, causing the controlling of the snake’s movements to progressively become more difficult.

Let’s build one!

Practical

Open Visual Studio and create a new Visual Basic Windows Forms project. Once your project has been created, design your form to resemble Figure 1:

Our Design
Figure 1: Our Design

This project needs a big Picturebox, one Label, and one Timer. Name the objects anything you desire, but keep in mind that my names may differ from yours.

Add a new class to your project. Name it clsMovement. Then, add the following Properties and Enum to clsMovement:

   Private pntLocation As Point
   Private intStep As Integer
   Private iDirection As intDirection

   Public ReadOnly Property Location() As Point

      Get

         Return pntLocation

      End Get

   End Property

   Public Property Direction() As intDirection

      Get

          Return iDirection

      End Get

      Set(ByVal Value As intDirection)

         iDirection = Value

      End Set

   End Property


   Public ReadOnly Property Increment() As Integer

      Get

         Return intStep

      End Get

   End Property

   Public Enum intDirection As Integer

      None = -1
      Left
      Down
      Right
      Up

   End Enum

These properties determine the direction the snake is moving, where it is, and by how many segments it should grow after it has eaten. Add the Constructors:

   Public Sub New()

      intStep = 8

      pntLocation = New Point(0, 0)

      Direction = intDirection.Right

   End Sub

   Public Sub New(ByVal iStep As Integer, ByVal pStart As Point, _
         ByVal dirNew As intDirection)

      iDirection = dirNew

      intStep = iStep

      pntLocation = pStart
   End Sub

Either one of these (depending on whether you have instantiated the class with parameters or without parameters) sets the default values of the direction, location, or growing objects. Add the following sub procedures:

   Public Function NextLoc(Optional ByVal dirNext _
         As intDirection = intDirection.None) As Point

      Dim pntLoc As New Point(pntLocation.X, pntLocation.Y)

      If (dirNext = intDirection.None) Then dirNext = iDirection

      Select Case dirNext

         Case intDirection.Left

            pntLoc.X -= intStep

         Exit Select

         Case intDirection.Down

            pntLoc.Y += intStep

         Exit Select

         Case intDirection.Right

            pntLoc.X += intStep

            Exit Select

         Case intDirection.Up

            pntLoc.Y -= intStep

            Exit Select

      End Select

      Return pntLoc

   End Function

   Public Sub Move(Optional ByVal dirMove As intDirection = _
         intDirection.None)

      If (dirMove = intDirection.None) Then dirMove = iDirection

      Select Case dirMove

         Case intDirection.Left

            pntLocation.X -= intStep

            Exit Select

         Case intDirection.Down

            pntLocation.Y += intStep

            Exit Select

         Case intDirection.Right

            pntLocation.X += intStep

            Exit Select

         Case intDirection.Up

            pntLocation.Y -= intStep

            Exit Select

      End Select

   End Sub

   Public Sub Move(ByVal rectBounds As Rectangle, Optional ByVal _
         dirMove As intDirection = intDirection.None)

      Move(dirMove)

      If (pntLocation.X > rectBounds.Right) Then

         pntLocation.X = CInt(rectBounds.Left / intStep) * intStep

      ElseIf (pntLocation.X < rectBounds.Left) Then

         pntLocation.X = CInt(rectBounds.Right / intStep) * intStep

      ElseIf (pntLocation.Y > rectBounds.Bottom) Then

         pntLocation.Y = CInt(rectBounds.Top / intStep) * intStep

      ElseIf (pntLocation.Y < rectBounds.Top) Then

         pntLocation.Y = CInt(rectBounds.Bottom / intStep) * _
            intStep

      End If

   End Sub

These subs are responsible for the snake object’s movement. They need to see where the snake currently is, which side of the screen to appear from next, as well as to keep track of which direction to move to, based upon the user’s key press.

Add a new class and name it clsSegment. Add the following properties to it:

   Private rectLoc As Rectangle

   Public ReadOnly Property Rect() As Rectangle

      Get

         Return rectLoc

      End Get

   End Property

   Public Property Loc() As Point

      Get

         Return rectLoc.Location

      End Get

      Set(ByVal Value As Point)

         rectLoc.Location = Value

      End Set

   End Property

   Public ReadOnly Property Size() As Size

      Get

         Return rectLoc.Size

      End Get

   End Property

These properties determine the current location as well as the size of the snake. Add the Constructor:

   Public Sub New(ByVal pntLoc As Point, ByVal intWidth As Integer)

      rectLoc = New Rectangle(pntLoc, New Size(intWidth, intWidth))

   End Sub

   Public Function CloneSegment() As clsSegment

      Return New clsSegment(rectLoc.Location, rectLoc.Width)

   End Function

   Public Overrides Function ToString() As String

      Return Me.GetType.ToString + ": " + rectLoc.Location.ToString

   End Function

Add a new class and name it clsSnake. Add the following members:

   Private Const intMaxLength As Integer = 1024
   Private Const intDefaultLength As Integer = 4
   Private Const intDefaultWidth As Integer = 8

   Private qSegments As Queue
   Private intWidth As Integer

The preceding code determines the maximum snake length, its starting length, and the snake’s width. It also creates a queue object to keep the new segments. Add the following properties:

   Public Property NumberOfSegments() As clsSegment()

      Get

         Dim cSegments(qSegments.Count - 1) As clsSegment

         qSegments.CopyTo(cSegments, 0)

         Return cSegments

      End Get

      Set(value As clsSegment())

      End Set

   End Property

   Public Property Head() As clsSegment

      Get

         Return DirectCast(qSegments.Peek, clsSegment).CloneSegment

      End Get

      Set(value As clsSegment)

      End Set

   End Property

Here, you keep track of the number of segments as well as where the snake’s head is. Add the constructor:

   Private Sub InitializeSnake(ByVal pntLoc As Point, _
         ByVal iWidth As Integer, ByVal iLength As Integer)

      intWidth = iWidth

      Dim pLoc As Point = pntLoc

      Dim i As Integer

      For i = 1 To iLength

         Eat(pLoc)

         pLoc.X -= intWidth

      Next

   End Sub

   Public Sub New()

      MyBase.New()

      InitializeSnake(New Point(intDefaultLength * _
         intDefaultWidth, 0), intDefaultWidth, intDefaultLength)

   End Sub

   Public Sub New(ByVal pntStart As Point, _
         ByVal iWidth As Integer, ByVal iLength As Integer)

      MyBase.New()

      InitializeSnake(pntStart, iWidth, iLength)

   End Sub

The New Constructor makes use of the InitializeSnake procedure to set all the defaults. Add the following sub procedures and Function:

   Public Sub Eat(ByVal pntLoc As Point)

      Dim cHead As New clsSegment(pntLoc, intWidth)

      If (qSegments Is Nothing) Then

         qSegments = New Queue(intMaxLength)

      ElseIf (qSegments.Count = intMaxLength) Then

         Move(pntLoc)

         Exit Sub

      End If

      qSegments.Enqueue(cHead)

   End Sub

   Public Sub Clear()

      qSegments.Clear()

   End Sub

   Public Sub Move(ByVal pntLoc As Point)

      Dim cHead As New clsSegment(pntLoc, intWidth

      qSegments.Enqueue(cHead)

      qSegments.Dequeue()

   End Sub


   Public Function FoodPlacedOnSnake(ByVal pntLoc As Point) _
         As Boolean

      Dim ieSegments As IEnumerator = qSegments.GetEnumerator

      While ieSegments.MoveNext

         If DirectCast(ieSegments.Current, clsSegment) _
            .Rect.Contains(pntLoc) Then Return True

      End While

   End Function

Eat allows the snake to eat and grow, Clear starts over, and Move completes the move. The FoodPlacedOnSnake function determines if the food has been placed on the snake; if so, we must place it somewhere else. Add the following members to your Form:

   Private Const intGrow As Integer = 3
   Private Const intWidth As Integer = 8

   Private cSnake As clsSnake
   Private cMovement As clsMovement

   Private blnMoving As Boolean = False
   Private blnExpanding As Boolean = False

   Private rectFood As Rectangle

   Private intScore As Integer

These members allow the snake to grow, expand, and keep track of the score for you. Add the following Functions and subs:

   Public Sub Feed()

      Dim pntFood As Point

      Do

         pntFood = Randomize()

         If Not (cSnake Is Nothing) Then

            If Not cSnake.FoodPlacedOnSnake(pntFood) Then Exit Do

         Else

            Exit Do

         End If

      Loop

         rectFood.Location = pntFood

   End Sub

   Private Sub Die()

      DisplayMessage("Press Enter to play or Escape to quit.")

      Initialize()

   End Sub


   Private Sub Initialize()

      intScore = 0

      rectFood = New Rectangle(0, 0, intWidth, intWidth)

      Feed()

      Dim pntStart As New Point(CInt(picGame.ClientSize.Width _
         / 2 / intWidth + 0.5) * intWidth, CInt(picGame _
         .ClientSize.Height / 2 / intWidth + 0.5) * intWidth)

      cSnake = New clsSnake(pntStart, intWidth, 1)

      cMovement = New clsMovement(intWidth, cSnake.Head.Loc, _
         clsMovement.intDirection.Right)

      blnExpanding = True

   End Sub

   Private Sub UpdateUI()

      Static iGrow As Integer = intGrow

      Static intAddSeg As Integer

      If Not blnMoving Then Exit Sub

      cMovement.Move(picGame.ClientRectangle)


      If cSnake.FoodPlacedOnSnake(cMovement.Location) Then

         iGrow = 0
         intAddSeg = 0

         Die()

         Return


      ElseIf rectFood.Contains(cMovement.Location) Then

         iGrow += intGrow

         blnExpanding = True

         Feed()

         intScore += 5

         Text = "Score: " + intScore.ToString

      End If

      If blnExpanding Then

         If iGrow < intGrow Then iGrow = intGrow

         If intAddSeg >= iGrow Then

            blnExpanding = False

            intAddSeg = 0
            iGrow = 0

            cSnake.Move(cMovement.Location)

         Else

            cSnake.Eat(cMovement.Location)

            intAddSeg += 1

            End If

      Else

         cSnake.Move(cMovement.Location)

      End If

   End Sub

   Private Sub DisplayMessage(ByVal strMsg As String)

      lblMessage.Text = strMsg
      lblMessage.Visible = True
      blnMoving = False

      tmrGame.Enabled = False

   End Sub

   Public Function Randomize() As Point

      Dim rnd As New Random(Now.Second)

      Dim intScreenWidth As Integer = ((ClientRectangle.Width \ _
         intWidth) - 2) * intWidth
      Dim intScreenHeight As Integer = ((ClientRectangle.Height \ _
         intWidth) - 2) * intWidth

      Dim intX As Integer = rnd.Next(0, intScreenWidth)
      Dim intY As Integer = rnd.Next(0, intScreenHeight)

      intX = (intX \ intWidth) * intWidth

      intY = (intY \ intWidth) * intWidth

      Return New Point(intX, intY)

   End Function

   Private Sub HideMessage()

      Me.Text = "Score: " + intScore.ToString

      lblMessage.Visible = False
      blnMoving = True
      tmrGame.Enabled = True

   End Sub

We feed the snake randomly until it dies. When the snake dies, we have to display a message with the score and start the game again. Add the events for Form1 that will enable us to draw the snake, its food, and to intercept keyboard messages:

   Private Sub Form1_Load(ByVal sender As Object, _
         ByVal e As System.EventArgs) Handles MyBase.Load

      Initialize()

   End Sub

   Private Sub picGame_Paint(ByVal sender As Object, ByVal e As _
         System.Windows.Forms.PaintEventArgs) Handles picGame.Paint

      If Not blnMoving Then

         e.Graphics.Clear(picGame.BackColor)

         Exit Sub

      End If

      e.Graphics.FillEllipse(Brushes.White, rectFood)

      Dim segCurrent As clsSegment


      For Each segCurrent In cSnake.NumberOfSegments

         e.Graphics.FillRectangle(Brushes.White, segCurrent.Rect)

      Next

   End Sub

   Private Sub tmrGame_Tick(ByVal sender As System.Object, _
         ByVal e As System.EventArgs) Handles tmrGame.Tick

      UpdateUI()

      picGame.Invalidate()

   End Sub

   Private Sub Form1_KeyUp(ByVal sender As Object, ByVal e As _
         System.Windows.Forms.KeyEventArgs) Handles MyBase.KeyUp

      Select Case e.KeyCode

         Case Keys.Enter

            HideMessage()

         Case Keys.Escape

            If blnMoving Then

               DisplayMessage("Press Enter to continue or Escape _
                  to quit.")

            Else

               Me.Close()

            End If

      End Select

   End Sub

   Private Sub Form1_KeyDown(ByVal sender As Object, ByVal e As _
         System.Windows.Forms.KeyEventArgs) Handles MyBase.KeyDown

      Select Case e.KeyCode

         Case Keys.Right

            cMovement.Direction = clsMovement.intDirection.Right

         Case Keys.Down

            cMovement.Direction = clsMovement.intDirection.Down

         Case Keys.Left

            cMovement.Direction = clsMovement.intDirection.Left

         Case Keys.Up

            cMovement.Direction = clsMovement.intDirection.Up

      End Select

   End Sub

The code for this article is available on GitHub.

Conclusion

I hope that you would agree that creating a snake game was quite fun. Most games are just logic—nothing else. After all, logic is what it is all about.

Hannes DuPreez
Hannes DuPreez
Ockert J. du Preez is a passionate coder and always willing to learn. He has written hundreds of developer articles over the years detailing his programming quests and adventures. He has written the following books: Visual Studio 2019 In-Depth (BpB Publications) JavaScript for Gurus (BpB Publications) He was the Technical Editor for Professional C++, 5th Edition (Wiley) He was a Microsoft Most Valuable Professional for .NET (2008–2017).

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read