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.
Snake.zip (102.84K)
Number of downloads: 735





MultiQuote




|