Page 1 of 1

Snake Using VB.Net

#1 AdamSpeight2008  Icon User is offline

  • MrCupOfT
  • member icon


Reputation: 2262
  • View blog
  • Posts: 9,464
  • Joined: 29-May 08

Posted 29 June 2010 - 04:24 PM

This tutorial documents how I created a simple version of the popular game Snake.

Let start with the basic game parameters.
GameState
Public Enum GameState As Integer
  GameOver = 0
  Playing = 1
  Paused = 2
End Enum


As you can see these are the basic state the game can be in.

GamingState
Public Enum GamingStates As Integer
  Demo = 0
  Player = 1
End Enum


GamingStates are related to who is playing.

MoveDirections
Public Enum MoveDirections As Integer
  North = 0
  East = 1
  South = 2
  West = 3
End Enum


The directions the snake can move in.


Building The Snake
Snake Parts
I've decided to build the snake up from smaller sections, you see way I did it this way later on).

Public Class SnakePart
  Protected Friend mColor As Color
  Protected Friend mRect As Rectangle
  Public Property Color() As Color
    Get
      Return mColor
    End Get
    Set(ByVal value As Color)
      mColor = value
    End Set
  End Property
  Public Sub New()
    mColor = Drawing.Color.Yellow
    mRect = New Rectangle(0, 0, 10, 10)
  End Sub
  Public Sub New(ByVal c As Color, ByVal x As Integer, ByVal y As Integer)
    mColor = c
    mRect = New Rectangle(x, y, 10, 10)
  End Sub
End Class


The Snake

Public Class Snake
  Public Event IsDead()
  Public Event EatenFood(ByRef f As Food)


These events are raised when the eats some food or when it dies.
  Protected Friend mHeading As MoveDirections = MoveDirections.East


Simple it store which way the head of the snake is facing.
  Protected Friend mHead As Color = Color.Yellow
  Protected Friend mBody As Color = Color.Green


Defines the colour of the head and body of the snake.

  Protected Friend mParts As New Queue(Of SnakePart)


This queue allows the positions of the snake parts to be remembered. How?

To move the snake, I push a new snake part on the end of the queue and remove a snake part from the beginning.
To grow the snake, I do the same, except for remove a snake part.

  Protected Friend mLand As SnakeGame


This let the snake access properties about the land the snake is in.

   Public Sub New(ByRef onLand As SnakeGame)
    mLand = onLand
    For i = 0 To 30 Step 10
      mParts.Enqueue(New SnakePart(Color.Green, i, 50))
    Next
    mParts.Last.Color = Color.Yellow
  End Sub


Initialises a snake.
  Public Property Heading() As MoveDirections
    Get
      Return mHeading
    End Get
    Set(ByVal value As MoveDirections)
      If [Enum].IsDefined(GetType(MoveDirections), value) Then mHeading = value
    End Set
  End Property



The following handles the movements of the snake. Does it need to grow? Has it died? etc.
  Public Sub Move(ByVal md As MoveDirections, Optional ByVal Lengthen As Boolean = False)
    If [Enum].IsDefined(GetType(MoveDirections), md) = False Then Throw New ArgumentException(String.Format("{0} is not a valid value.", md))
    mHeading = md
    If mParts.Count > 0 Then
      mParts.Last.Color = mBody
      Dim sp As SnakePart = Nothing
      Select Case md
        Case MoveDirections.North : sp = New SnakePart(Color.Yellow, mParts.Last.mRect.X, mParts.Last.mRect.Y - mLand.mSize)
        Case MoveDirections.East : sp = New SnakePart(Color.Yellow, mParts.Last.mRect.X + mLand.mSize, mParts.Last.mRect.Y)
        Case MoveDirections.South : sp = New SnakePart(Color.Yellow, mParts.Last.mRect.X, mParts.Last.mRect.Y + mLand.mSize)
        Case MoveDirections.West : sp = New SnakePart(Color.Yellow, mParts.Last.mRect.X - mLand.mSize, mParts.Last.mRect.Y)
      End Select
      Dim ft As IEnumerable(Of Food) = mLand.mFood.Where(Function(fp As Food) fp.Rect.IntersectsWith(sp.mRect))
      If ft.Any Then
        Lengthen = True
        RaiseEvent EatenFood(ft(0))
       End If
      If Not Lengthen Then mParts.Dequeue()
      Dim MoveIntoSelf = mParts.Any(Function(dd As SnakePart) dd.mRect.IntersectsWith(sp.mRect))
      ' Check to see if snake is dead
      If MoveIntoSelf Then RaiseEvent IsDead()
      If sp.mRect.X < mLand.mBounds.Left Then RaiseEvent IsDead()
      If sp.mRect.X > mLand.mBounds.Right Then RaiseEvent IsDead()
      If sp.mRect.Y < mLand.mBounds.Top Then RaiseEvent IsDead()
      If sp.mRect.Y > mLand.mBounds.Bottom Then RaiseEvent IsDead()
      mParts.Enqueue(sp)
    End If
  End Sub


Now to draw the snake.
  Public Sub DrawSnake(ByRef g As Graphics)
    For Each sp As SnakePart In mParts
      Using b As New Pen(sp.Color)
        g.FillRectangle(b.Brush, sp.mRect)
        g.DrawRectangle(Pens.Black, sp.mRect)
      End Using
    Next
  End Sub
End Class


The Game Logic
This class handles all of the game logic for the snake game.
Public Class SnakeGame
  Protected Friend mBounds As New Rectangle(0, 25, 300, 300)
  Protected Friend mSize As Integer = 10
  Protected Friend mRnd As New Random
  Protected Friend WithEvents mSnake As Snake
  Protected Friend mFood As New List(Of Food)
  Protected Friend mGameState As GameState
  Protected Friend mInitialFood As Integer = 4
  Protected Friend mPoints As Integer = 0

  Protected Friend mTick As New System.Windows.Forms.Timer With {.Interval = 500, .Enabled = True}


I had to use the class of timer, as for some unknown reason the other doesn't work right.

#Region "Events"
  Public Event GameOver()
  Public Event Updated()
#End Region


GameOver is raised when the snake dies.
Updated is raised when there has been an update, so the land can be redrawn.

#Region "ReadOnly Properties"
  Public ReadOnly Property Points As Integer
    Get
      Return mPoints
    End Get
  End Property
  Public ReadOnly Property GameState
    Get
      Return mGameState
    End Get
  End Property
#End Region
  Private Sub Tick(ByVal sender As System.Object, ByVal e As System.EventArgs)
    If GameState = GameState.Playing Then Move()
  End Sub
  Public Sub New()
    ' Initialise a new Snake
    mSnake = New Snake(Me)
    ' Populate the world with some food.
    For i = 1 To mInitialFood
      AddNewPieceOfFood()
    Next
    AddHandler mSnake.IsDead, AddressOf HandleDeadSnake
    AddHandler mSnake.EatenFood, AddressOf HasEatenFood
    mGameState = GameState.Playing
    AddHandler mTick.Tick, AddressOf Tick
  End Sub
  Public Sub Pause()
    If mGameState = GameState.Playing Then
      mGameState = GameState.Paused
    ElseIf mGameState = GameState.Paused Then
      mGameState = GameState.Playing
    End If
  End Sub
  Private Sub HasEatenFood(ByRef f As Food)
    mFood.Remove(f)
    mPoints += 1
    If mFood.Any = False Then AddNewPieceOfFood()
  End Sub
  Private Sub HandleDeadSnake() Handles mSnake.IsDead
    mGameState = GameState.GameOver
    RaiseEvent GameOver()
  End Sub
  Public Sub Move()
    Move(mSnake.Heading)
  End Sub
  Public Sub Move(ByVal md As MoveDirections)
    mSnake.Move(md)
    If mRnd.NextDouble <= 0.1 Then AddNewPieceOfFood()
    RaiseEvent Updated()
  End Sub
  Private Sub AddNewPieceOfFood()
    Dim x As Integer = 0
    Dim y As Integer = 0
     Dim fr As Food = Nothing
    Dim OverFood As Boolean = True
    Dim OverSnake As Boolean = True
    Do
      x = mRnd.Next(0, 20)
      y = mRnd.Next(0, 20)
      fr = New Food(Me, Color.Purple, x * mSize, y * mSize)
      OverFood = mFood.Any(Function(f As Food) f.Rect.IntersectsWith(fr.Rect))
      OverSnake = mSnake.mParts.Any(Function(s As SnakePart) s.mRect.IntersectsWith(fr.Rect))
    Loop Until Not (OverFood OrElse OverSnake)
    mFood.Add(New Food(Me, fr.Color, fr.Rect.X, fr.Rect.Y))
  End Sub
  Public Sub Draw(ByRef g As Graphics)
    For Each fp As Food In mFood
      fp.Draw(g)
    Next
    mSnake.DrawSnake(g)
  End Sub
End Class



A simple class to define food.
Public Class Food
  Protected Friend mColor As Color
  Protected Friend mRect As Rectangle
  Public ReadOnly Property Color() As Color
    Get
      Return mColor
    End Get
  End Property
  Public ReadOnly Property Rect As Rectangle
    Get
      Return mRect
    End Get
  End Property

  Public Sub New(ByRef land As SnakeGame, ByVal c As Color, ByVal x As Integer, ByVal y As Integer)
    mColor = c
    mRect = New Rectangle(x + land.mBounds.Left, y + land.mBounds.Top, land.mSize, land.mSize)
  End Sub
  Public Sub Draw(ByRef g As Graphics)
    Using b As New Pen(mColor)
      g.FillEllipse(b.Brush, mRect)
      g.DrawEllipse(Pens.Black, mRect)
    End Using
  End Sub
End Class



Putting It All Together
Now let's create the actual form for the game, so we can play a game.

Public Class Frm_SnakeGame
  Protected Friend mGs As GamingStates = GamingStates.Player
  Private WithEvents PlayingGame As SnakeGame
  Private WithEvents PlayerGame As SnakeGame



The follow section handles the button press, which in turn moves the snake.
  Private Sub Form1_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown
    If PlayingGame.GameState = GameState.Playing Then
      Select Case e.KeyCode
        Case Keys.Up : PlayingGame.Move(MoveDirections.North)
        Case Keys.Right : PlayingGame.Move(MoveDirections.East)
        Case Keys.Down : PlayingGame.Move(MoveDirections.South)
        Case Keys.Left : PlayingGame.Move(MoveDirections.West)
      End Select
    End If
  End Sub

  Public Sub HandleGameUpdate()
    Me.Txt_Score.Text = PlayingGame.Points
    Me.Invalidate()
  End Sub

  Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
    If PlayingGame IsNot Nothing Then PlayingGame.Draw(e.Graphics)
  End Sub

  Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs)
    If PlayingGame.GameState = GameState.Playing Then PlayingGame.Move()
  End Sub

  Public Sub HandleGameOver()
    Me.But_NewGame.Enabled = True
    Me.But_Pause.Visible = False
  End Sub


New Game Button
  Private Sub NewGameButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles But_NewGame.Click
    Txt_Score.Text = 0
    But_NewGame.Enabled = False
    But_Pause.Visible = True
    PlayerGame = New SnakeGame
    AddHandler PlayerGame.GameOver, AddressOf HandleGameOver
    AddHandler PlayerGame.Updated, AddressOf HandleGameUpdate
    PlayingGame = PlayerGame
  End Sub



Add a Pause Button
  Private Sub But_Pause_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles But_Pause.Click
    PlayingGame = PlayerGame
    If PlayerGame.GameState = GameState.Paused Then
      PlayingGame = PlayerGame
      PlayingGame.Pause()
    Else
      PlayingGame.Pause()

    End If
  End Sub
End Class



A Demo Player
The follow is my attempt at adding a demonstration mode the game.
Public Class DemoPlayer
  Protected Friend mGame As SnakeGame
  Protected Friend mRnd As New Random
  Protected Friend mDemoPlay As New System.Windows.Forms.Timer With {.Interval = 500, .Enabled = True}
  Public Sub New(ByRef g As SnakeGame)
    mGame = g
    AddHandler mDemoPlay.Tick, AddressOf TheDemoPlayer
    AddHandler mGame.GameOver, AddressOf GameOver
  End Sub
  Public Sub GameOver()
    mGame = New SnakeGame
  End Sub
  Private Sub TheDemoPlayer(ByVal sender As System.Object, ByVal e As System.EventArgs)
    Dim n = mRnd.NextDouble
    Select Case mGame.mSnake.Heading
      Case MoveDirections.North
        Select Case n
          Case n <= 0.3 : mGame.Move(MoveDirections.North)
          Case (n > 0.3) AndAlso (n <= 0.6) : mGame.Move(MoveDirections.East)
          Case (n > 0.6) AndAlso (n <= 0.9) : mGame.Move(MoveDirections.West)
          Case n > 0.9 : mGame.Move(MoveDirections.South)
        End Select
      Case MoveDirections.East
        Select Case True
          Case n <= 0.3 : mGame.Move(MoveDirections.East)
          Case (n > 0.3) AndAlso (n <= 0.6) : mGame.Move(MoveDirections.North)
          Case (n > 0.6) AndAlso (n <= 0.9) : mGame.Move(MoveDirections.South)
          Case n > 0.9 : mGame.Move(MoveDirections.West)
        End Select
      Case MoveDirections.South
        Select Case n
          Case n <= 0.3 : mGame.Move(MoveDirections.South)
          Case (n > 0.3) AndAlso (n <= 0.6) : mGame.Move(MoveDirections.East)
          Case (n > 0.6) AndAlso (n <= 0.9) : mGame.Move(MoveDirections.West)
          Case n > 0.9 : mGame.Move(MoveDirections.North)
        End Select
      Case MoveDirections.West
        Select Case n
          Case n <= 0.3 : mGame.Move(MoveDirections.West)
          Case (n > 0.3) AndAlso (n <= 0.6) : mGame.Move(MoveDirections.South)
          Case (n > 0.6) AndAlso (n <= 0.9) : mGame.Move(MoveDirections.North)
          Case n > 0.9 : mGame.Move(MoveDirections.East)
        End Select
    End Select
    My.Application.DoEvents()
  End Sub
End Class




Add a button to the toolbar (But_Demo) and the following code to form.
  Dim demoGame As SnakeGame
  Dim demo As DemoPlayer
  Private Sub But_Demo_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles But_Demo.Click
    If PlayingGame IsNot Nothing Then PlayingGame.Pause()
    demoGame = New SnakeGame
    AddHandler demoGame.Updated, AddressOf Me.HandleGameUpdate
    mGs = GamingStates.Demo
    PlayingGame = demoGame
    demo = New DemoPlayer(PlayingGame)

  End Sub


End Class


You'll see that if you start a new game and pause it, the click the demo button, then unpause the game. That player game hasn't be destroyed.

Written using the VB2010 Express Edition.
Attached File  Snake.zip (102.84K)
Number of downloads: 1155

Is This A Good Question/Topic? 3
  • +

Replies To: Snake

#2 JBrace1990  Icon User is offline

  • D.I.C Addict
  • member icon

Reputation: 110
  • View blog
  • Posts: 760
  • Joined: 09-March 08

Posted 29 June 2010 - 08:08 PM

Long, but nicely done.

For future tutorials, I would suggest possibly explaining the code a little more so it is easier for beginners, but I do like this. Good job =)
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1