Reactive Programming (IV.) - Developing a game in Reactive LINQ
In this part of the article series about Reactive LINQwe're going to implement a slightly more complicated application using the library that I introduced in the previous three articles. We're going to use basic event stream queries from the second article as well as advanced operators introduced in the third part. This time, I'll also show the F# version of all the examples, so we're going to build on the ideas from the first part.
I originally wanted to write the demo only in Visual Basic, because I think that it is really amazig to show an idea that came from functional programming in a language that noone (maybe until recently) connects with functional programming. Then I realized that I really want to show the F# version too, because F# was an inspiration for the whole Reactive LINQ idea and it is interesting alone as well. But finally, I thought that don't showing the C# version may look offensive to many readers (especially since I'm still C# MVP...). So, I ended up writing the game in all three languages, but the code is surprisingly similar in all of them! I decided to make the blog high-tech today, so you can choose the language in which the samples will be displayed here (but don't worry, you can switch the language for every single sample below as well). The language displayed by default is still Visual Basic though!
- Switch the source code to C#!
- Switch the source code to Visual Basic!
- Switch the source code to F#!
- You can download all the source code below...
Implementing Reactive LINQ game
Your goal is to click on the smiley face and the game counts the number of clicks. When you click on the smiley or after 600ms it moves to another random location. The game also counts the time and once the time reaches zero, the game ends and displays the final score. You can see my result on the screenshot, but you should get much better score, because I was using touchpad when playing...
Structing the code
One of the interesting things about Reactive LINQ is that it gives us an excellent way to decompose the problem and implement several parts of the game almost independently (indeed, this is one of the benefits of functional reactive programming, which inspired the project). We'll start by writing code that counts the number of clicks on the smiley face and displaying this as a score. As a next step, we'll implement moving of the smiley and finally we'll add a few lines of code to count the time and stop the game.
Counting clicks
We've already seen how to count the number of clicks on a button in the previous article.
Counting the number of clicks on the smiley face will be very similar. We'll count the number of clicks
on an element that shows the smiley, but we'll also check whether the click was inside the face (circle).
Then we'll use aggregation to get an event stream yielding the total number of clicks so far.
In Visual Basic, we can use the Aggregate
query syntax, in C# we can use LINQ query
only for filtering using where
and the F# version uses only pipelining (|>
)
and high-order functions:
Visual C#Visual F#Visual BasicCounting the number of clicks
' Display the number of clicks
Dim eClicked = _
Aggregate md In PicSmiley.AttachEvent(Of MouseEventArgs)("MouseDown") _
Where md.Button = MouseButtons.Left _
Where Math.Pow(PicSmiley.Width / 2 - md.X, 2) + _
Math.Pow(PicSmiley.Height / 2 - md.Y, 2) < 2250 _
Into Sum(1)
eClicked.Listen(Function(sum) _
SetText(LblScore, String.Format("Score: {0}", sum)))
// Display the number of clicks
var eClicked =
(from md in picSmiley.AttachEvent<MouseEventArgs>("MouseDown")
where md.Button == MouseButtons.Left
where Math.Pow(picSmiley.Width / 2 - md.X, 2) +
Math.Pow(picSmiley.Height / 2 - md.Y, 2) < 2250
select 1).Sum().Pass(sum =>
lblScore.Text = String.Format("Score: {0}", sum));
// Utility function that creates 'constant function'
let cns n = (fun _ -> n)
// Display the number of clicks
let eClicked =
picSmiley.MouseDown
|> Reactive.freeable
|> Reactive.filter (fun md -> md.Button = MouseButtons.Left)
|> Reactive.filter (fun md ->
pown (float picSmiley.Width / 2. - float md.X) 2 +
pown (float picSmiley.Height / 2. - float md.Y) 2 < 2250.0 )
|> Reactive.sumBy (cns 1)
|> Reactive.pass (sprintf "Score: %d" >> lblScore.set_Text)
|> Reactive.map (cns ())
There are a few things that deserve some explanation. First of all, we're using Pass
extension method in the C# version. This is very similar to Listen
with the exception
that it returns the original event, so we can store it for further use in a local variable eClicked
.
In the Visual Basic version, we don't use anything new. There are a few things to note about the F# version,
but if you're not interested in F#, you can skip the following paragraph.
In the F# version, we're using functions from Reactive
module (while in the first article
we've been using Event
). This is because many of the functions that we're using aren't currently
available in F#, so I reimplemented them in the Reactive
module. The Reactive.freeable
function converts an F# event into an event representation used by Reactive.Xyz
functions, so
we'll use it as a first one in every sequence. In F#, we also use map
at the end to produce
an event stream that will contain unit
values, that is values that don't carry any meaning
- we're just interested in knowing when the event fired.
Moving the smiley
As a next step, we'll implement moving of the smiley. The smiley moves when the user clicks on it,
so we'll need to use the event eClicked
from the previous listing. However, the smiley
also moves after 600ms if the user hasn't clicked on it yet. This is quite similar to one of the examples
from the previous article - the one where we were displaying
"Click..." and "Timeout!" messages.
We'll need to use one of the switching operators. We want to generate a new event stream every time
an event occurs, to make sure that the timer will start counting from zero. This can be done using
SwitchRecursive
operator. As an input stream, we'll give it an event that is fired some
time after the application starts and then it starts generating events using the provided function.
In our example (in C# and VB), we'll use the query syntax. The part that specifies how to generate
an event when switching occurs will use Reactive.Merge
to produce an event that happens
when the user clicks or after 600ms timeout. This means that every time the user clicks on the smiley,
the event stream will be regenerated and the user will have another 600ms to click on the image:
Visual C#Visual F#Visual BasicMoving the smiley
' Moves the smiley every time the user clicked on it or after specified time
Dim rnd = New Random()
Dim eMoveSmiley = _
From e In Reactive.After(1000, 0) _
From s In Reactive.Merge(eClicked, Reactive.After(600.0, 0)).UseSwitchRecursive() _
Select New Point _
(rnd.Next(ClientSize.Width - PicSmiley.Width), _
20 + rnd.Next(ClientSize.Height - PicSmiley.Height - 20))
eMoveSmiley.Listen(AddressOf MoveSmiley)
// Moves the smiley every time the user clicked on it or after specified time
var rnd = new Random();
var eMoveSmiley =
(from e in Reactive.After(1000, 0)
from s in Reactive.Merge(eClicked, Reactive.After(600.0, 0)).UseSwitchRecursive()
select new Point
(rnd.Next(ClientSize.Width - picSmiley.Width),
20 + rnd.Next(ClientSize.Height - picSmiley.Height - 20)))
.Pass(pos => picSmiley.Location = pos);
// Moves the smiley every time the user clicked on it or after specified time
let rnd = new Random()
let eMoveSmiley =
Reactive.After(1000.0, ())
|> Reactive.switchRecursive (fun () ->
Reactive.Merge(eClicked, Reactive.After(600.0, ())))
|> Reactive.map (fun _ ->
Point(rnd.Next(frm.ClientSize.Width - picSmiley.Width),
20 + rnd.Next(frm.ClientSize.Height - picSmiley.Height - 20)) )
|> Reactive.pass picSmiley.set_Location
In C# and VB, the eClicked
event yields integer values. To make the types compatible,
our calls to Reactive.After
also generate an integer value (the second argument specifies
the value that should be generated after the specified time and we set it to 0). In F#, we're using
a unit
value instead, so we generate a value "()
" (which represents no information).
The result of the recursive switch operator is an event, which is triggered every time the smiley
should move. Then we generate a new position. In C# and VB, this is done inside the query and in F#
this is done using a call to map
operator (which is just like Select
).
Once we have a new location, we can set the position of the smiley control (either using Pass
or using Listen
).
Also note that we could test this code separately from the previous code snippet very well. The
only thing that wouldn't be available is the eClicked
event, so we couldn't test
whether the smiley reacts correctly to a click. However, we could simulate this by reacting to
a click to any place on the form (just to test whether the movement is correct). This ability to
separate parts of the behavior is clearly one of the interesting aspects of this project.
In the next section, we'll implement the third part of the game, which is again to a large extent
separate from what we've implemented so far.
Counting the time and showing results
The remaining part of our game will count the time elapsed from the start of the game
and display this time on a label. We'll do this simply by using Reactive.Repeatedly
,
which allows us to create an event that is triggered every second. Once we have an event that
tells us the current time, we can also handle the end of the game. When the time is less than
zero, we'll display a message with the result and stop all event processing.
Visual C#Visual F#Visual BasicCounting the time and showing results
' Count-down timer, shows the time on a label
Dim start = DateTime.Now
Dim eCountDown = _
From e In Reactive.Repeatedly(1000) _
Let sec = 20 - e.Subtract(start).TotalSeconds _
Select New With _
{.Time = sec, .Message = _
String.Format("Time: {0}", CType(sec, Integer))}
eCountDown.Listen(Function(s) SetText(LblTime, s.Message))
' Stop the game when time is less than zero
eCountDown _
.Where(Function(t) t.Time <= 0) _
.Listen(Function() StopGame(eClicked, eMoveSmiley, eCountDown))
// Count-down timer, shows the time on a label
var start = DateTime.Now;
(from e in Reactive.Repeatedly(1000)
let sec = 20 - e.Subtract(start).TotalSeconds
select new { Time = sec, Message =
String.Format("Time: {0}", (int)sec) })
.Pass(s => lblTime.Text = s.Message)
.Where(t => t.Time <= 0).First()
.Listen(_ => {
eClicked.Stop();
eMoveSmiley.Stop();
MessageBox.Show("Game over!\n" + lblScore.Text);
});
// Count-down timer, shows the time on a label
let start = DateTime.Now
Reactive.Repeatedly(1000.0)
|> Reactive.map (fun e -> 20.0 - e.Subtract(start).TotalSeconds)
|> Reactive.pass (int >> sprintf "Time: %d" >> lblTime.set_Text)
|> Reactive.filter ((>) 0.0)
|> Reactive.first
|> Reactive.listen(fun _ ->
eClicked.Stop()
eMoveSmiley.Stop()
MessageBox.Show("Game over!\n" + lblScore.Text) |> ignore )
The event processing stream starts with a call to Reactive.Repeatedly
, which
generates event that is raised every second. Then we calculate the number of seconds until
the game ends. In C# and Visual Basic we return an anonymous type that contains
the number of seconds and also a message that we want to display to the user. This could
be done without anonymous type as well (we could simply build the string in the subsequent
Listen
or Pass
), but I wanted to demonstrate how to use anonymous
types in Reactive LINQ, because this is often useful.
In the F# version, we use function composition when displaying the message. The argument
to the Reactive.pass
operator is a function composed from three different
functions. The first one (int
) converts a float number to an integer. The
next one (sprintf
) formats a message and the last one is set_Text
,
which sets a .NET property of the label.
Once we set the label, we continue processing (in Visual Basic, this is written as a separate
statement). We wait until the time reported from the event stream is less then or equal to zero,
which means that the game has ended. In the reaction to this event, we call Stop
method on all event streams that we're processing. This releases all event handlers, so the
game will be stopped by that. The current implementation doesn't allow running the game multiple
times, but this could be done simply by wrapping all the code in a method and running it again
to restart the game.
Summary
In this article, we've seen how to implement a simple (but not completely trivial) game using Reactive LINQ. Thanks to the use of LINQ and lambda functions, the code is more declarative meaning that it describes more what you want to do rather than how do you want to do it. For example when we specify how the smiley should move, we don't have to create any timer and set its properties. The code just specifies that we want to do something after 600ms unless something else happens first.
Another interesting thing about the project is that you can develop various parts of the application independently. For example, the game we implemented here was divided into three parts (counting clicks, moving the smiley and counting the time). When developing the game, all of them can be developed almost independently and then you connect them together in a places where this is needed (such as when you want to move the smiley when the user clicks on it).
Finally, let me again shortly mention that the current implementation is not very well tested and will probably need some design changes. However, I hope that the idea looks interesting and I'd be very glad to hear some feedback if you'll download the code and play with it. I'll definitely continue working on this project, so I'll write some more articles about it and I'm also investigating how the implementation could be improved. So, stay tuned...
Series links
- Reactive programming (I.) - First class events in F#
- Reactive programming (II.) - Introducing Reactive LINQ
- Reactive Programming (III.) - Useful Reactive LINQ Operators
- Reactive Programming (IV.) - Developing reactive game in Reactive LINQ
Published: Monday, 24 November 2008, 3:00 AM
Author: Tomas Petricek
Typos: Send me a pull request!
Tags: c#, functional