TP

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!

Click on the smiley!
(Click for a full-size screenshot)

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

Published: Monday, 24 November 2008, 3:00 AM
Author: Tomas Petricek
Typos: Send me a pull request!
Tags: c#, functional