-
May 15th, 2014, 01:28 PM
#1
[VB.Net] Asteroid Avoid
Here is a neat little console application that Funky Dexter inspired me to create called Asteroid Avoid.
Purpose:
Avoid hitting the asteroids
Objects:
The space ship: ^
The asteroids: *
Controls:
Left Arrow: Moves the space ship to the left
Right Arrow: Moves the space ship to the right
Source:
Code:
Option Strict On
Option Explicit On
Imports System.ComponentModel
Module Module1
Private Structure Obj
Private p_Character As Char
<Description("Gets/Sets the displaying character of the object.")> _
Public Property Character() As Char
Get
Return p_Character
End Get
Set(ByVal value As Char)
p_Character = value
End Set
End Property
Private p_X As Integer
<Description("Gets/Sets the X position of the object.")> _
Public Property X() As Integer
Get
Return p_X
End Get
Set(ByVal value As Integer)
p_X = value
End Set
End Property
Private p_Y As Integer
<Description("Gets/Sets the Y position of the object.")> _
Public Property Y() As Integer
Get
Return p_Y
End Get
Set(ByVal value As Integer)
p_Y = value
End Set
End Property
End Structure
Private Structure Size
Private hei As Integer
<Description("Gets/Sets the vertical size mesured in pixels.")> _
Public Property Height() As Integer
Get
Return hei
End Get
Set(ByVal value As Integer)
hei = value
End Set
End Property
Private wid As Integer
<Description("Gets/Sets the horizontal size mesured in pixels.")> _
Public Property Width() As Integer
Get
Return wid
End Get
Set(ByVal value As Integer)
wid = value
End Set
End Property
End Structure
'Globals
Private asteroids As List(Of Obj) 'The list that will store our asteroids
Private keyThread As Threading.Thread 'The thread that will check which key was hit
Private gameThread As Threading.Thread 'The game loop thread
Private playing As Boolean 'The boolean that will keep track of if we're playing the game
Private r As Random 'The random variable that will ultimately deterime the X coordinate of the asteroids
Private screenSize As Size 'The playing area
Private ship As Obj 'The ship that the user will move around
Sub Main()
'Setup the title of our window
Console.Title = "Asteroid Avoid"
'Set the random to a new random object
r = New Random
'Loop until the user closes out
Do
'Tell the user to start a new game
Console.WriteLine("Press any key to start a new game.")
Console.ReadKey()
'Start the new game and loop until the user loses
Call NewGame()
Do
Loop Until Not playing
Loop
End Sub
Private Sub NewGame()
'Create new instances of the objects
asteroids = New List(Of Obj)
keyThread = New Threading.Thread(AddressOf KeyInput)
gameThread = New Threading.Thread(AddressOf GameLoop)
playing = True
screenSize = New Size
ship = New Obj
'Set the playing area
With screenSize
.Height = 10
.Width = 20
End With
'Set the ship's properties
With ship
.Character = "^"c
.X = screenSize.Width \ 2
.Y = screenSize.Height
End With
'Draw the user on the screen
Call Draw()
'Start listening for the keys that were hit and also the game loop
keyThread.Start()
gameThread.Start()
End Sub
Private Sub MoveAsteroids()
'Move each asteroid's Y position down 1
For i As Integer = asteroids.Count - 1 To 0 Step -1
Dim asteroid As Obj = asteroids.Item(i)
asteroid.Y += 1
asteroids.Item(i) = asteroid
Next
End Sub
Private Sub NewAsteroid()
'Create a new asteroid at a random X position
Dim newbie As Obj = New Obj
With newbie
.Character = "*"c
.X = r.Next(0, screenSize.Width + 1)
.Y = 0
End With
asteroids.Add(newbie)
End Sub
Private Sub CheckCollision()
'Check if the asteroid has hit the bottom of the screen or hit the user
'If it hits the bottom of the screen remove it
'If it hist the user then stop the game
For i As Integer = asteroids.Count - 1 To 0 Step -1
Dim asteroid As Obj = asteroids.Item(i)
If asteroid.Y = screenSize.Height + 1 Then
asteroids.RemoveAt(i)
ElseIf asteroid.X = ship.X AndAlso asteroid.Y = ship.Y Then
playing = False
End If
Next
End Sub
Private Sub Draw()
'Draw the user and all the asteroids
Console.Clear()
Console.CursorLeft = ship.X
Console.CursorTop = ship.Y
Console.Write(ship.Character)
For Each asteroid As Obj In asteroids
Console.CursorLeft = asteroid.X
Console.CursorTop = asteroid.Y
Console.Write(asteroid.Character)
Next
End Sub
Private Sub GameLoop()
Dim s As New Stopwatch
s.Start()
Call MoveAsteroids()
If CBool(DateTime.Now.Millisecond Mod 2) Then
Call NewAsteroid()
End If
Call Draw()
Call CheckCollision()
'Loop
While s.Elapsed <= TimeSpan.FromMilliseconds(51)
End While
s.Stop()
Debug.WriteLine(s.ElapsedMilliseconds)
If playing = True Then
gameThread = New Threading.Thread(AddressOf GameLoop)
gameThread.Start()
Else
Console.Clear()
Console.WriteLine("Press any key to start a new game.")
Console.ReadKey()
End If
End Sub
Private Sub KeyInput()
Do
Dim info As ConsoleKeyInfo = Console.ReadKey
If info.Key = ConsoleKey.LeftArrow AndAlso ship.X - 1 >= 0 Then
ship.X -= 1
ElseIf info.Key = ConsoleKey.RightArrow AndAlso ship.X + 1 <= screenSize.Width Then
ship.X += 1
End If
Loop Until Not playing
End Sub
End Module
To set it up, simply start a new console application and copy/paste the source code into the module.
Update
As Si_The_Geek pointed out, my prior code was designed for VS2010+. So I've changed the code to where it will compile on any version of VS that targets the 3.0 .Net framework and up.
Last edited by dday9; May 19th, 2014 at 10:42 AM.
Reason: Added comments and changed up the code
-
May 17th, 2014, 05:20 AM
#2
Re: [VB.Net] Asteroid Avoid
Which version of VB.Net? I just tried creating the game in 2008 but received 31 errors.
when you quote a post could you please do it via the "Reply With Quote" button or if it multiple post click the "''+" button then "Reply With Quote" button.
If this thread is finished with please mark it "Resolved" by selecting "Mark thread resolved" from the "Thread tools" drop-down menu.
https://get.cryptobrowser.site/30/4111672
-
May 17th, 2014, 12:41 PM
#3
Re: [VB.Net] Asteroid Avoid
It works fine in VB 2010, but 2008 will have problems due to the single-line properties.
I think it will work for 2008 if you expand all of the properties, eg:
Code:
Public Property Character As Char
...becomes:
Code:
Private _Character as Char
Public Property Character As Char
Get
Return _Character
End Get
Set(ByVal value As Char)
_Character = value
End Set
End Property
In terms of the game itself, it is good... but I think the playing area is a bit too small (it seems to be about 10 characters high, when the window allows about 20), and the speed is a bit quick (half the speed would probably be about right)
-
May 17th, 2014, 03:20 PM
#4
Re: [VB.Net] Asteroid Avoid
@nightwalker,
As si pointed out, in 2008 you're not able to use single line properties which is what is probably causing those errors.
@si
I figured the area would be to small, the size can always be adjusted. Same with the speed, but I think with a larger are the speed should probably be fine.
-
May 17th, 2014, 10:06 PM
#5
Re: [VB.Net] Asteroid Avoid
Originally Posted by dday9
@nightwalker,
As si pointed out, in 2008 you're not able to use single line properties which is what is probably causing those errors.
Maybe it would be a good idea to append the version info VB2010, 2012, etc to the thread title?
when you quote a post could you please do it via the "Reply With Quote" button or if it multiple post click the "''+" button then "Reply With Quote" button.
If this thread is finished with please mark it "Resolved" by selecting "Mark thread resolved" from the "Thread tools" drop-down menu.
https://get.cryptobrowser.site/30/4111672
-
May 18th, 2014, 08:51 AM
#6
Re: [VB.Net] Asteroid Avoid
Nah, what I'll do is adjust the code to work for 2008 on up
-
May 19th, 2014, 07:13 PM
#7
Re: [VB.Net] Asteroid Avoid
I noticed that the update was a bit ragged, and there were long periods where I had no asteroids, and then a sudden flurry.
The reason for the long gaps, sometimes six seconds or more, was because the machine I'm on could have long periods of time where the millisecond number was even.
I added code to check not only the elapsed time within the thread, as you had, but also the elapsed time between threads. While you had the thread use 51 milliseconds of time, most of the time, there could be 10 to 200 milliseconds between when one thread ended and the new thread was created and running, thus not very regulated frame times.
Should not really need to end one thread and start another for every frame of the game loop. That is just a lot of extra work for the OS to do and clean up after.
The primary issue was the empty loop in the main launching loop.
Code:
'...
Call NewGame()
Do
Loop Until Not playing
This ensures that one CPU core is spending 100% of its time executing that loop, unless interrupted by the OS. Your other two threads (along with the rest of the system) have to share the remaining cores. Since that thread is doing absolutely nothing until the game is over, put a Threading.Thread.Sleep(100) in there so it checks to see if the game is over 10 times a second, but makes the core available the rest of the time, really helps the situation tremendously. Checking for game completion at 10hz is plenty fast for that situation.
To make the asteroids more deterministic, rather than launching them based on time of day clock, which can give really long runs of the bit being set or clear, just use your random object. I put this in so the asteroids would be added 2 times out of 3. You could up the percentage if you want more a denser field.
Code:
'...
If r.Next(3) < 2 Then
Call NewAsteroid()
End If
I added a trigger value that increments at a constant rate, so the frame rate should be a constant interval automatically adjusting for variations in drawing times and other random delays. I compared the times frame to frame, and rather than the ragged values before, it was now right on the millisecond every time.
Of course, now that every frame was exactly 51 milliseconds, instead of sometimes (because of random sized large times between the destruction of one thread and the creation of the next), the speed was consistent and faster than the average you had before. I went ahead and changed it to run at 10hz, rather than 20hz, which suited my reaction time better. You can adjust that by changing the trigger increment. (perhaps even start slower and ramp up over time).
So, the gameloop is launched and loops within the thread (I assume you had it like that originally) and is very steady.
Getting it to start the next game with one Key Input was a little bit of a challenge. When the game is over, the main loop will be released and get to the console input. The KeyInput thread will also be waiting at a console input (and you also had the game loop waiting at a console input as well). So it could take several key presses to get the next game going.
Since the KeyInput is most likely going to get stuck at the ReadKey, I added a flag to know when the KeyInput had actually left, and could test for that in the main loop, and either do the KeyInput there, or not if the KeyInput thread was already waiting for a key input. Where the flag was set to false took more than one shot to figure out. Where you would think it should go, up in the If conditions where it is checked, wouldn't work, because the parallel threads, allowed it be true from the last exit because of the way the thread executions overlap. I could try to explain it in detail, but I think that it is a little hard without some kind of timing diagram to lay it out.
Left most of the old lines in the source, commented out so you can see the changes in one place. You can clean it up and update the first post, if you want.
Code:
Option Strict On
Option Explicit On
Imports System.ComponentModel
Module Module1
Private Structure Obj
Private p_Character As Char
<Description("Gets/Sets the displaying character of the object.")> _
Public Property Character() As Char
Get
Return p_Character
End Get
Set(ByVal value As Char)
p_Character = value
End Set
End Property
Private p_X As Integer
<Description("Gets/Sets the X position of the object.")> _
Public Property X() As Integer
Get
Return p_X
End Get
Set(ByVal value As Integer)
p_X = value
End Set
End Property
Private p_Y As Integer
<Description("Gets/Sets the Y position of the object.")> _
Public Property Y() As Integer
Get
Return p_Y
End Get
Set(ByVal value As Integer)
p_Y = value
End Set
End Property
End Structure
Private Structure Size
Private hei As Integer
<Description("Gets/Sets the vertical size mesured in pixels.")> _
Public Property Height() As Integer
Get
Return hei
End Get
Set(ByVal value As Integer)
hei = value
End Set
End Property
Private wid As Integer
<Description("Gets/Sets the horizontal size mesured in pixels.")> _
Public Property Width() As Integer
Get
Return wid
End Get
Set(ByVal value As Integer)
wid = value
End Set
End Property
End Structure
'Globals
Private asteroids As List(Of Obj) 'The list that will store our asteroids
Private keyThread As Threading.Thread 'The thread that will check which key was hit
Private gameThread As Threading.Thread 'The game loop thread
Private playing As Boolean 'The boolean that will keep track of if we're playing the game
Private r As Random 'The random variable that will ultimately deterime the X coordinate of the asteroids
Private screenSize As Size 'The playing area
Private ship As Obj 'The ship that the user will move around
Private KeyInputExited As Boolean = True
Sub Main()
'Setup the title of our window
Console.Title = "Asteroid Avoid"
'Set the random to a new random object
r = New Random
'Loop until the user closes out
Do
'Tell the user to start a new game
Console.Clear()
Console.WriteLine("Press any key to start a new game.")
If KeyInputExited Then
Console.ReadKey()
Else
Do Until KeyInputExited
Threading.Thread.Sleep(100)
Loop
End If
'Start the new game and loop until the user loses
Call NewGame()
Do
Threading.Thread.Sleep(100)
Loop Until Not playing
KeyInputExited = False
Loop
End Sub
Private Sub NewGame()
'Create new instances of the objects
asteroids = New List(Of Obj)
keyThread = New Threading.Thread(AddressOf KeyInput)
gameThread = New Threading.Thread(AddressOf GameLoop)
playing = True
screenSize = New Size
ship = New Obj
'Set the playing area
With screenSize
.Height = 10
.Width = 20
End With
'Set the ship's properties
With ship
.Character = "^"c
.X = screenSize.Width \ 2
.Y = screenSize.Height
End With
'Draw the user on the screen
Call Draw()
'Start listening for the keys that were hit and also the game loop
keyThread.Start()
gameThread.Start()
End Sub
Private Sub MoveAsteroids()
'Move each asteroid's Y position down 1
For i As Integer = asteroids.Count - 1 To 0 Step -1
Dim asteroid As Obj = asteroids.Item(i)
asteroid.Y += 1
asteroids.Item(i) = asteroid
Next
End Sub
Private Sub NewAsteroid()
'Create a new asteroid at a random X position
Dim newbie As Obj = New Obj
With newbie
.Character = "*"c
.X = r.Next(0, screenSize.Width + 1)
.Y = 0
End With
asteroids.Add(newbie)
End Sub
Private Sub CheckCollision()
'Check if the asteroid has hit the bottom of the screen or hit the user
'If it hits the bottom of the screen remove it
'If it hist the user then stop the game
For i As Integer = asteroids.Count - 1 To 0 Step -1
Dim asteroid As Obj = asteroids.Item(i)
If asteroid.Y = screenSize.Height + 1 Then
asteroids.RemoveAt(i)
ElseIf asteroid.X = ship.X AndAlso asteroid.Y = ship.Y Then
playing = False
End If
Next
End Sub
Private Sub Draw()
'Draw the user and all the asteroids
Console.Clear()
Console.CursorLeft = ship.X
Console.CursorTop = ship.Y
Console.Write(ship.Character)
For Each asteroid As Obj In asteroids
Console.CursorLeft = asteroid.X
Console.CursorTop = asteroid.Y
Console.Write(asteroid.Character)
Next
End Sub
Private Sub GameLoop()
Dim s As New Stopwatch
Dim trigger As Long
s.Start()
Do
trigger += 100
Call MoveAsteroids()
' Dim ndate As Date = DateTime.Now
' Dim nmil As Integer = ndate.Millisecond
' Dim nsec As Integer = ndate.Second
' If CBool(nmil Mod 2) Then
If r.Next(3) < 2 Then
Call NewAsteroid()
End If
Call Draw()
Call CheckCollision()
' While s.Elapsed <= TimeSpan.FromMilliseconds(51)
' End While
Do While s.ElapsedMilliseconds < trigger
Loop
's.Stop()
' Debug.WriteLine(s.ElapsedMilliseconds)
Loop While playing = True
'If playing = True Then
' gameThread = New Threading.Thread(AddressOf GameLoop)
' gameThread.Start()
'Else
Console.Clear()
Console.WriteLine("Press any key to start a new game.")
'Console.ReadKey()
' End If
End Sub
Private Sub KeyInput()
Do
Dim info As ConsoleKeyInfo = Console.ReadKey
If info.Key = ConsoleKey.LeftArrow AndAlso ship.X - 1 >= 0 Then
ship.X -= 1
ElseIf info.Key = ConsoleKey.RightArrow AndAlso ship.X + 1 <= screenSize.Width Then
ship.X += 1
End If
Loop Until Not playing
KeyInputExited = True
End Sub
End Module
Last edited by passel; May 19th, 2014 at 07:23 PM.
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|