Today’s post is a brief example of how to implement a game using F# and SignalR. Creating a game for bots to play doesn’t have to be overly difficult. Since interesting emergent qualities can arise from simple rules, it makes for a fun way to show off SignalR, beyond the standard chat application. As this post will show, F# and SignalR work well together to create a nice communication framework without requiring a complex setup.
What is the game? It is a bot-played game of multi-player snakes. The rules are simple: eat food to grow, and run into opponents to slice off their tails. To give players a goal, they accrue points based on their length over time. It is a limited enough concept that a game engine and client can be built without overshadowing the SignalR aspects. A picture, or movie, is worth a thousand words. So below is a sample of the game player viewer. What is SignalR? If you’re not familiar, it is a library that provides real-time capabilities to web applications. Think websockets and other related technologies. In this particular case there is a web viewer and a console app leveraging the capability.
With definitions out of the way, time for the technical components. We’ll use .NET Core version 2.2. If you don’t have it installed, head out to the .NET Core Downloads page. Select SDK for your platform. Tangential, but you can also get here by going to dot.net, then navigating to
The post will be broken up into 3 primary parts: SignalR server, SignalR client, SignalR webviewer. Discussing the specific game code will be out of scope, since it is the interactions that we really care about.
For the server, Giraffe will be the base. It will host the SignalR services as well as the weS viewer. Creation is similiar to a typical dotnet app, but it’ll use the Giraffe template. If you need the templates you can get them by doing
dotnet new -i "giraffe-template::*". The Giraffe template includes a reference to the
Microsoft.AspNetCore.App package, which includes SignalR, so no additional packages are necessary.
dotnet new giraffe -lang F# -n GameServer -o GameServer
The Giraffe templates thankfully generate all the necessary boilerplate code for a webapp on top of Kestrel. To simplify, we’ll focus on the components that need to be added to the server code. Add the necessary namespaces, this is not only for SignalR, but to support the background game engine service.
The SignalR components must be added to the pipeline. This is done in two places. Modify
configureApp to include
configureServices to include
services.AddSignalR(). In addition, the game runs as a hosted service. To support this, modify
configureServices to also includ
let configureApp (app : IApplicationBuilder) =
Now that the components have been injected into the pipeline, they need to be created. For this we’ll need to create a SignalR hub as well as a GameService. Starting with the SignalR hub. We can send messages to the SignalR clients by supplying a function name and payload:
this.Clients.All.SendAsync("Message", "foo"). But, we can do better by defining the interface and making the calls type-safe, so let’s do that. Below is defined the client api interface. This ensures that calls from server to client match the required types. For simplicity, the server only has 3 messages it can send to clients.
LoginResponseReports success or failure, and their PlayerId if login was successful.
MessageSends general notifications to clients.
GameStateProvides a serialized gamestate that clients act on.
type IClientApi =
Now, to define the SignalR hub. This effectively is the listener that all clients connect to. It leverages the
IClientApi that was just created. Here we need to write the handlers for messages accepted from clients. Players have four different actions they can signal to the server.
LoginFor brevity, there is no authentication; provide a PlayerName and they get a PlayerId. It also adds a player to the game. The below code demonstrates how the server can send messages to all connected clients or just specific ones.
LogoutRemoves a player from the game.
TurnPlayers have one action they can perform, turn. They move in a specified direction until they turn, then they proceed in that direction.
SendPlayers can blast messages to all clients. Perhaps when the bots become self-aware they can taunt each other.
type GameHub () =
Now that the SignalR hub is done, it’s time to make the GameService that performs the server-side game logic as well as sending updated gamestate to players. For this a background service is used. At a set interval it processes current game state
updateState and sends it out to all clients. One note here: because I’ve choosen to use a client interface, the hub context is defined as
IHubContext<GameHub, IClientApi>). If this wasn’t the case, it would be defined as
IHubContext<GameHub> and messages would be sent using
type GameService (hubContext :IHubContext<GameHub, IClientApi>) =
Beyond the specific game logic implementation, that’s all there is to the SignalR server. It now will send out gamestate updates as well as handle client messages.
The next step is building the client. To do this, a dotnet console app will be created, and then the SignalR package is added.
dotnet new console -lang f# -n ClientFs
Once that is done, it needs the SignalR namespace.
The client needs to make a connection to the SignalR hub. Similar to the server, the client needs some event handlers for server generated messages.
LoginResponseA successful login gives the client a playerId.
Message- Handle general message notifications.
GameState- When the server sends the current gamestate, the client evaluates and then sends an action message back.
Closed- When the connection closes, what does the client do? In this case attempts to reconnect.
Once the event handlers are setup, the client connects and performs a login. The handlers take care of the rest. As can be seen below, the client uses
InvokeAsync to send messages to the server (as seen in the login).
The handler logic is uninteresting, but it is useful to see the definitions that match with the handlers. In addition, I’ve included the client’s response back to the server in the gameState handler. Again, it uses InvokeAsync when contacting the server.
let loginResponseHandler (connection :HubConnection) (success :bool) (id :string) =
The final piece to address is the game viewer. This comes in two parts: the layout and the code. For the layout, we leverage Giraffe’s view engine. It’s a simple view that contains an html canvas map, player list, messages display, and a state print (for debugging purposes). This is also where supporting js libraries: signalr, jquery, as well as the viewer game-server.js are included. For this project, the files reside in the
module Views =
This may bring up a question, where did
signalr.js come from? Well, there is one more thing we need to add to the project. In a real project I’d package this differently, but a quick and dirty way will do for now.
npm install @aspnet/signalr
/// SignalR connection