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:

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.