Coding a Euchre Game, Part 3: Timers
In my previous posts regarding the Euchre game creation, I discussed some issues with creating a complex form and maintaining images to be shown on it. In this posting, I’m going to start covering some of the more esoteric controls.
Timers and Message Pumps
Try this: go to your Programs menu in Windows and launch the “Hearts” game. Play a few hands of it. Go ahead, I’ll wait.
(hum-dee-dum-dee-dum)
You back yet? Notice anything a little odd about that game? The action is just a little fast, isn’t it? In fact it’s so fast that you can barely take it in, and it takes you a few seconds just to realize what the other players have done. That’s just too fast for me, and although I enjoy playing Hearts on my handheld (where it’s much better paced), I simply can’t play it on my desktop – it just feels unnatural.
Hearts is fast because the AI players simply connect their actions from one to the other without pauses. Real players don’t act like that, though. To simulate real player hesitation, we’re going to use a timer control.
To start this, we simply drag a Timer control from the toolbox to the EuchreTable form. You’ll note that the timer control does not actually land on the form, but instead goes to a grey area below the form. That’s because timers don’t actually have real positions; they’re never visible, so it makes no sense to put the on the form itself. However, you still need to be able to set properties on them,which is why they show up in that grey area. (Controls such as these are essentially identical to the old concept of a “windowless control.”)
For the timer control, we’ll change its name to “EuchreTimer” and set the Interval to 1500 (measured in milliseconds) as a default — we can always adjust it later. Now, in the constructor for EuchreTable, we’ll add the following line of code:
AddHandler EuchreTimer.Tick, AddressOf TimerEventProcessor
which specifies the handler for the Tick event which will occur every 1.5 seconds.
Now, we don’t want the timer to be on all the time. We just want it to fire when the AI is making a decision. So, by default, the timer will be off, and we’ll turn it on when needed. After it fires once, we’ll turn it off again.
Private Sub TimerEventProcessor(ByVal myObject As Object, _
ByVal myEventArgs As EventArgs)
EuchreTimer.Stop()
End Sub
When we want to use the timer, we simply turn it on. Let’s put the call in a method called TimerSleep which anyone can call to start up the timer:
Private Sub TableSleep()
EuchreTimer.Start()
End Sub
However, now we have a problem. The timer doesn’t actually block the AI from proceeding – it just throws out a “tick” which will be handled. To actually block the AI from proceeding, we’re going to need to wait for the tick. Let’s create a member variable call GoAhead and add a check for that to the two routines we’ve defined:
Private Sub TimerEventProcessor(ByVal myObject As Object, _
ByVal myEventArgs As EventArgs)
EuchreTimer.Stop()
GoAhead = True
End Sub
Private Sub TableSleep()
GoAhead = False
EuchreTimer.Start()
While GoAhead = False
End While
End Sub
Cool, now anyone calling TableSleep will block until the value of GoAhead changes when the timer event is processed, right? Well, no. The timer uses the UI message pump, and with the code as it currently is, the Timer message won’t be processed. There are other messages that we’d want to have handled as well, such as those involving painting the application. So, we’re going to need to pump messages. This is extremely easy to do by using the Application.DoEvents() method:
Private Sub TableSleep()
StopPumpingMessagesDuringPause = False
EuchreTimer.Start()
While GoAhead = False
Application.DoEvents()
End While
End Sub
Now, what if the user exited the game (or start a new one) while the timer was functioning? We’d like to turn off the timer immediately, rather than have them wait to exit. We do this by handling the “Closed” event (or the New Game message, as applicable). First, we add a handler in the constructor of EuchreTable:
AddHandler Me.Closed, AddressOf Me.EuchreTable_Closed
Then we implement the handler:
Private Sub EuchreTable_Closed(ByVal sender As Object, _
ByVal e As EventArgs)
GoAhead = True
exitEuchre = True
End Sub
And then we’ll alter the TableSleep method to throw an exception so that we can exit the game logic, no matter where we are. This is a realy important step – as we are in the middle of a game, it would be bad if the form went away while we were still executing in the middle of a hand! I’ve created a class called EuchreException which just makes it easier for me to determine that it’s one of my own exceptions and not a system exception:
Class EuchreException
Inherits SystemException
Public Sub New(ByVal message As String)
MyBase.New(message)
End Sub
End Class
Private Sub TableSleep()
StopPumpingMessagesDuringPause = False
EuchreTimer.Start()
While GoAhead = False
Application.DoEvents()
End While
If exitEuchre = True Then
Throw New EuchreException(“ExitGame”)
End If
End Sub
And in the main loop of the program (which I call “NewGameInvoked”), we catch the exception and force an exit to the application:
Private Sub NewGameInvoked()
Try
Do While exitEuchre = False
‘ (this is where the game actually happens –
I’ve omitted the actual code for brevity’s sake)
Loop
Catch ex As EuchreException
If ex.Message = “ExitGame” Then
exitEuchre = False
‘ Do nothing; fall through and exit the application
End If
End Try
End Sub
This may seem a little complicated for a game – after all, who cares if they have to wait a couple of seconds for the game to exit? Or why not just let the AI players operate at warp speed? But, at the very least, we *have* to periodically check for messages or we’ll never repaint or be able to exit the app until the game ends. You might think that we could have simply
0 comments
Be the first to start the discussion.