Learning is more fun if we do it while making games. With this thought, let's continue our quest to learn .NET Core 2.0 by writing a Tic-tac-toe game in .NET Core 2.0. We will develop the game in the ASP.NET Core 2.0 web app, using SignalR Core. We will follow a step-by-step approach and use Visual Studio 2017 as the primary IDE, but will list the steps needed while using the Visual Studio Code editor as well.
Let's do the project setup first and then we will dive into the coding.
This tutorial has been extracted from the book .NET Core 2.0 By Example, by Rishabh Verma and Neha Shrivastava.
Create a new ASP.NET Core 2.0 MVC app named TicTacToeGame.
With this, we will have a basic working ASP.NET Core 2.0 MVC app in place. However, to leverage SignalR Core in our app, we need to install SignalR Core NuGet and the client packages.
To install the SignalR Core NuGet package, we can perform one of the following two approaches in the Visual Studio IDE:
<ItemGroup> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" /> <PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.0.0-alpha1-final" /> </ItemGroup>
Now we have server-side packages installed. We still need to install the client-side package of SignalR, which is available through npm. To do so, we need to first ascertain whether we have npm installed on the machine or not. If not, we need to install it. npm is distributed with Node.js, so we need to download and install Node.js from https://nodejs.org/en/. The installation is quite straightforward.
Once this installation is done, open a Command Prompt at the project location and run the following command:
npm install @aspnet/signalr-client
This will install the SignalR client package. Just go to the package location (npm creates a node_modules folder in the project directory). The relative path from the project directory would be \node_modules\@aspnet\signalr-client\dist\browser.
From this location, copy the signalr-client-1.0.0-alpha1-final.js file into the wwwroot\js folder. In the current version, the name is signalr-client-1.0.0-alpha1-final.js.
With this, we are done with the project setup and we are ready to use SignalR goodness as well. So let's dive into the coding.
In this section, we will implement our gaming solution. The end output will be the working two-player Tic-Tac-Toe game. We will do the coding in steps for ease of understanding:
//// Adds SignalR to the services container. services.AddSignalR();
//// Use - SignalR & let it know to intercept and map any request having gameHub. app.UseSignalR(routes => { routes.MapHub<GameHub>("gameHub"); });
The following is the code for both methods, for the sake of clarity and completion. Other methods and properties are removed for brevity:
// This method gets called by the run-time. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddMvc(); //// Adds SignalR to the services container. services.AddSignalR(); }
// This method gets called by the runtime. Use this method to
configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment
env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
//// Use - SignalR & let it know to intercept and map any request
having gameHub.
app.UseSignalR(routes =>
{
routes.MapHub<GameHub>("gameHub");
});
/// <summary> /// The player class. Each player of Tic-Tac-Toe game would be an instance of this class. /// </summary> internal class Player { /// <summary> /// Gets or sets the name of the player. This would be set at the time user registers. /// </summary> public string Name { get; set; }
/// <summary>
/// Gets or sets the opponent player. The player
against whom the player would be playing.
/// This is determined/ set when the players click Find
Opponent Button in the UI.
/// </summary>
public Player Opponent { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the player
is playing.
/// This is set when the player starts a game.
/// </summary>
public bool IsPlaying { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the player
is waiting for opponent to make a move.
/// </summary>
public bool WaitingForMove { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the player
is searching for opponent.
/// </summary>
public bool IsSearchingOpponent { get; set; }
/// <summary>
/// Gets or sets the time when the player registered.
/// </summary>
public DateTime RegisterTime { get; set; }
/// <summary>
/// Gets or sets the image of the player.
/// This would be set at the time of registration, if
the user selects the image.
/// </summary>
public string Image { get; set; }
/// <summary>
/// Gets or sets the connection id of the player
connection with the gameHub.
/// </summary>
public string ConnectionId { get; set; }
}
<script src="~/lib/jquery/dist/jquery.js"></script> <!-- jQuery--> <script src="~/js/signalr-client-1.0.0-alpha1-final.js"></script> <!-- SignalR-->
After adding these references, create the simple HTML UI for the image preview and registration, as follows:
<div id="divPreviewImage"> <!-- To display the browsed image--> <fieldset> <div class="form-group"> <div class="col-lg-2"> <image src="" id="previewImage" style="height:100px;width:100px;border:solid 2px dotted; float:left" /> </div> <div class="col-lg-10" id="divOpponentPlayer"> <!-- To display image of opponent player--> <image src="" id="opponentImage" style="height:100px;width:100px;border:solid 2px dotted; float:right;" /> </div> </div> </fieldset> </div>
<div id="divRegister"> <!-- Our Registration form-->
<fieldset>
<legend>Register</legend>
<div class="form-group">
<label for="name" class="col-lg-2 control-
label">Name</label>
<div class="col-lg-10">
<input type="text" class="form-control" id="name"
placeholder="Name">
</div>
</div>
<div class="form-group">
<label for="image" class="col-lg-2 control-
label">Avatar</label>
<div class="col-lg-10">
<input type="file" class="form-control" id="image"
/>
</div>
</div>
<div class="form-group">
<div class="col-lg-10 col-lg-offset-2">
<button type="button" class="btn btn-primary"
id="btnRegister">Register</button>
</div>
</div>
</fieldset>
</div>
let hubUrl = '/gameHub'; let httpConnection = new signalR.HttpConnection(hubUrl); let hubConnection = new signalR.HubConnection(httpConnection); var playerName = ""; var playerImage = ""; var hash = "#"; hubConnection.start();
$("#btnRegister").click(function () { //// Fires on button click
playerName = $('#name').val(); //// Sets the player name
with the input name.
playerImage = $('#previewImage').attr('src'); //// Sets the
player image variable with specified image
var data = playerName.concat(hash, playerImage); //// The
registration data to be sent to server.
hubConnection.invoke('RegisterPlayer', data); //// Invoke
the "RegisterPlayer" method on gameHub.
});
$("#image").change(function () { //// Fires when image is changed.
readURL(this); //// HTML 5 way to read the image as data
url.
});
function readURL(input) {
if (input.files && input.files[0]) { //// Go in only if
image is specified.
var reader = new FileReader();
reader.onload = imageIsLoaded;
reader.readAsDataURL(input.files[0]);
}
}
function imageIsLoaded(e) {
if (e.target.result) {
$('#previewImage').attr('src', e.target.result); ////
Sets the image source for preview.
$("#divPreviewImage").show();
}
};
/// <summary> /// The Game Hub class derived from Hub /// </summary> public class GameHub : Hub { /// <summary> /// To keep the list of all the connected players registered with the game hub. We could have /// used normal list but used concurrent bag as its thread safe. /// </summary> private static readonly ConcurrentBag<Player> players = new ConcurrentBag<Player>();
/// <summary>
/// Registers the player with name and image.
/// </summary>
/// <param name="nameAndImageData">The name and image data
sent by the player.</param>
public void RegisterPlayer(string nameAndImageData)
{
var splitData = nameAndImageData?.Split(new char[] {
'#' }, StringSplitOptions.None);
string name = splitData[0];
string image = splitData[1];
var player = players?.FirstOrDefault(x =>
x.ConnectionId == Context.ConnectionId);
if (player == null)
{
player = new Player { ConnectionId =
Context.ConnectionId, Name = name, IsPlaying =
false, IsSearchingOpponent = false, RegisterTime =
DateTime.UtcNow, Image = image };
if (!players.Any(j => j.Name == name))
{
players.Add(player);
}
}
this.OnRegisterationComplete(Context.ConnectionId);
}
/// <summary>
/// Fires on completion of registration.
/// </summary>
/// <param name="connectionId">The connectionId of the
player which registered</param>
public void OnRegisterationComplete(string connectionId)
{
//// Notify this connection id that the registration
is complete.
this.Clients.Client(connectionId).
InvokeAsync(Constants.RegistrationComplete);
}
}
The code comments make it self-explanatory. The class should derive from the SignalR Hub class for it to be recognized as Hub.
There are two methods of interest which can be overridden. Notice that both the methods follow the async pattern and hence return Task:
There are also a few properties that the hub class exposes:
To keep the things simple, we are not using a database to keep track of our registered players. Rather we will use an in-memory collection to keep the registered players. We could have used a normal list of players, such as List<Player>, but then we would need all the thread safety and use one of the thread safety primitives, such as lock, monitor, and so on, so we are going with ConcurrentBag<Player>, which is thread safe and reasonable for our game development.
That explains the declaration of the players collection in the class. We will need to do some housekeeping to add players to this collection when they resister and remove them when they disconnect.
We saw in previous step that the client invoked the RegisterPlayer method of the hub on the server, passing in the name and image data. So we defined a public method in our hub, named RegisterPlayer, accepting the name and image data string concatenated through #.
This is just one of the simple ways of accepting the client data for demonstration purposes, we can also use strongly typed parameters. In this method, we split the string on # and extract the name as the first part and the image as the second part. We then check if the player with the current connection ID already exists in our players collection.
If it doesn't, we create a Player object with default values and add them to our players collection. We are distinguishing the player based on the name for demonstration purposes, but we can add an Id property in the Player class and make different players have the same name also. After the registration is complete, the server needs to update the player, that the registration is complete and the player can then look for the opponent.
To do so, we make a call to the OnRegistrationComplete method which invokes a method called registrationComplete on the client with the current connection ID. Let's understand the code to invoke the method on the client:
this.Clients.Client(connectionId).InvokeAsync(Constants.RegistrationComplete);
On the Clients property, we can choose a client having a specific connection ID (in this case, the current connection ID from the Context) and then call InvokeAsync to invoke a method on the client specifying the method name and parameters as required. In the preceding case method, the name is registrationComplete with no parameters.
Now we know how to invoke a server method from the client and also how to invoke the client method from the server. We also know how to select a specific client and invoke a method there. We can invoke the client method from the server, for all the clients, a group of clients, or a specific client, so rest of the coding stuff would be just a repetition of these two concepts.
In both the cases, the server would do some processing and invoke a method on the client. Since we need a lot of different user interfaces for different scenarios, let's code the HTML markup inside div to make it easier to show and hide sections based on the server response. We will add the following code snippet in the body. The comments specify the purpose of each of the div elements and markup inside them:
<div id="divFindOpponentPlayer"> <!-- Section to display Find Opponent --> <fieldset> <legend>Find a player to play against!</legend> <div class="form-group"> <input type="button" class="btn btn-primary" id="btnFindOpponentPlayer" value="Find Opponent Player" /> </div> </fieldset> </div> <div id="divFindingOpponentPlayer"> <!-- Section to display opponent not found, wait --> <fieldset> <legend>Its lonely here!</legend> <div class="form-group"> Looking for an opponent player. Waiting for someone to join! </div> </fieldset> </div> <div id="divGameInformation" class="form-group"> <!-- Section to display game information--> <div class="form-group" id="divGameInfo"></div> <div class="form-group" id="divInfo"></div> </div> <div id="divGame" style="clear:both"> <!-- Section where the game board would be displayed --> <fieldset> <legend>Game On</legend> <div id="divGameBoard" style="width:380px"></div> </fieldset> </div>
The following client-side code would take care of Steps 7 and 8. Though the comments are self-explanatory, we will quickly see what all stuff is that is going on here. We handle the registartionComplete method and display the Find Opponent Player section. This section has a button to find an opponent player called btnFindOpponentPlayer.
We define the event handler of the button to invoke the FindOpponent method on the hub. We will see the hub method implementation later, but we know that the hub method would either find an opponent or would not find an opponent, so we have defined the methods opponentFound and opponentNotFound, respectively, to handle these scenarios. In the opponentNotFound method, we just display a section in which we say, we do not have an opponent player.
In the opponentFound method, we display the game section, game information section, opponent display picture section, and draw the Tic-Tac-Toe game board as a 3×3 grid using CSS styling. All the other sections are hidden:
$("#btnFindOpponentPlayer").click(function () { hubConnection.invoke('FindOpponent'); });
hubConnection.on('registrationComplete', data => { //// Fires on registration complete. Invoked by server hub
$("#divRegister").hide(); // hide the registration div
$("#divFindOpponentPlayer").show(); // display find opponent
player div.
});
hubConnection.on('opponentNotFound', data => { //// Fires when no opponent is found.
$('#divFindOpponentPlayer').hide(); //// hide the find
opponent player section.
$('#divFindingOpponentPlayer').show(); //// display the
finding opponent player div.
});
hubConnection.on('opponentFound', (data, image) => { //// Fires
when opponent player is found.
$('#divFindOpponentPlayer').hide();
$('#divFindingOpponentPlayer').hide();
$('#divGame').show(); //// Show game board section.
$('#divGameInformation').show(); //// Show game information
$('#divOpponentPlayer').show(); //// Show opponent player
image.
opponentImage = image; //// sets the opponent player image
for display
$('#opponentImage').attr('src', opponentImage); //// Binds
the opponent player image
$('#divGameInfo').html("<br/><span><strong> Hey " +
playerName + "! You are playing against <i>" + data + "</i>
</strong></span>"); //// displays the information of
opponent that the player is playing against.
//// Draw the tic-tac-toe game board, A 3x3 grid :) by
proper styling.
for (var i = 0; i < 9; i++) {
$("#divGameBoard").append("<span class='marker' id=" + i
+ " style='display:block;border:2px solid
black;height:100px;width:100px;float:left;margin:10px;'>"
+ i + "</span>");
}
});
First we need to have a Game object to track a game, players involved, moves left, and check if there is a winner. We will have a Game class defined as per the following code. The comments detail the purpose of the methods and the properties defined:
internal class Game { /// <summary> /// Gets or sets the value indicating whether the game is over. /// </summary> public bool IsOver { get; private set; }
/// <summary>
/// Gets or sets the value indicating whether the
game is draw.
/// </summary>
public bool IsDraw { get; private set; }
/// <summary>
/// Gets or sets Player 1 of the game
/// </summary>
public Player Player1 { get; set; }
/// <summary>
/// Gets or sets Player 2 of the game
/// </summary>
public Player Player2 { get; set; }
/// <summary>
/// For internal housekeeping, To keep track of value in each
of the box in the grid.
/// </summary>
private readonly int[] field = new int[9];
/// <summary>
/// The number of moves left. We start the game with 9 moves
remaining in a 3x3 grid.
/// </summary>
private int movesLeft = 9;
/// <summary>
/// Initializes a new instance of the
<see cref="Game"/> class.
/// </summary>
public Game()
{
//// Initialize the game
for (var i = 0; i < field.Length; i++)
{
field[i] = -1;
}
}
/// <summary>
/// Place the player number at a given position for a player
/// </summary>
/// <param name="player">The player number would be 0 or
1</param>
/// <param name="position">The position where player number
would be placed, should be between 0 and
///8, both inclusive</param>
/// <returns>Boolean true if game is over and
we have a winner.</returns>
public bool Play(int player, int position)
{
if (this.IsOver)
{
return false;
}
//// Place the player number at the given position
this.PlacePlayerNumber(player, position);
//// Check if we have a winner. If this returns true,
//// game would be over and would have a winner, else game
would continue.
return this.CheckWinner();
}
}
Now we have the entire game mystery solved with the Game class. We know when the game is over, we have the method to place the player marker, and check the winner. The following server side-code on the GameHub will handle Steps 7 and 8:
/// <summary> /// The list of games going on. /// </summary> private static readonly ConcurrentBag<Game> games = new ConcurrentBag<Game>();
/// <summary>
/// To simulate the coin toss. Like heads and tails, 0 belongs to
one player and 1 to opponent.
/// </summary>
private static readonly Random toss = new Random();
/// <summary>
/// Finds the opponent for the player and sets the Seraching for
Opponent property of player to true.
/// We will use the connection id from context to identify the
current player.
/// Once we have 2 players looking to play, we can pair them and
simulate coin toss to start the game.
/// </summary>
public void FindOpponent()
{
//// First fetch the player from our players collection having
current connection id
var player = players.FirstOrDefault(x => x.ConnectionId ==
Context.ConnectionId);
if (player == null)
{
//// Since player would be registered before making this
call,
//// we should not reach here. If we are here, something
somewhere in the flow above is broken.
return;
}
//// Set that player is seraching for opponent.
player.IsSearchingOpponent = true;
//// We will follow a queue, so find a player who registered
earlier as opponent.
//// This would only be the case if more than 2 players are
looking for opponent.
var opponent = players.Where(x => x.ConnectionId !=
Context.ConnectionId && x.IsSearchingOpponent &&
!x.IsPlaying).OrderBy(x =>x.RegisterTime).FirstOrDefault();
if (opponent == null)
{
//// Could not find any opponent, invoke opponentNotFound
method in the client.
Clients.Client(Context.ConnectionId)
.InvokeAsync(Constants.OpponentNotFound);
return;
}
//// Set both players as playing.
player.IsPlaying = true;
player.IsSearchingOpponent = false; //// Make him unsearchable
for opponent search
opponent.IsPlaying = true;
opponent.IsSearchingOpponent = false;
//// Set each other as opponents.
player.Opponent = opponent;
opponent.Opponent = player;
//// Notify both players that they can play by invoking
opponentFound method for both the players.
//// Also pass the opponent name and opoonet image, so that
they can visualize it.
//// Here we are directly using connection id, but group is a
good candidate and use here.
Clients.Client(Context.ConnectionId)
.InvokeAsync(Constants.OpponentFound, opponent.Name,
opponent.Image);
Clients.Client(opponent.ConnectionId)
.InvokeAsync(Constants.OpponentFound, player.Name,
player.Image);
//// Create a new game with these 2 player and add it to
games collection.
games.Add(new Game { Player1 = player, Player2 = opponent });
}
Here, we have created a games collection to keep track of ongoing games and a Random field named toss to simulate the coin toss. How FindOpponent works is documented in the comments and is intuitive to understand.
//// Triggers on clicking the grid cell. $(document).on('click', '.marker', function () { if ($(this).hasClass("notAvailable")) { //// Cell is already taken. return; }
hubConnection.invoke('MakeAMove', $(this)[0].id); //// Cell is
valid, send details to hub.
});
//// Fires when player has to make a move.
hubConnection.on('waitingForMove', data => {
$('#divInfo').html("<br/><span><strong> Your turn <i>" +
playerName + "</i>! Make a winning move! </strong></span>");
});
//// Fires when move is made by either player. hubConnection.on('moveMade', data => { if (data.Image == playerImage) { //// Move made by player. $("#" + data.ImagePosition).addClass("notAvailable"); $("#" + data.ImagePosition).css('background-image', 'url(' + data.Image + ')'); $('#divInfo').html("<br/><strong>Waiting for <i>" + data.OpponentName + "</i> to make a move. </strong>"); } else
{ $("#" + data.ImagePosition).addClass("notAvailable"); $("#" + data.ImagePosition).css('background-image', 'url(' + data.Image + ')'); $('#divInfo').html("<br/><strong>Waiting for <i>" + data.OpponentName + "</i> to make a move. </strong>"); } });
//// Fires when the game ends.
hubConnection.on('gameOver', data => {
$('#divGame').hide();
$('#divInfo').html("<br/><span><strong>Hey " + playerName +
"! " + data + " </strong></span>");
$('#divGameBoard').html(" ");
$('#divGameInfo').html(" ");
$('#divOpponentPlayer').hide();
});
//// Fires when the opponent disconnects.
hubConnection.on('opponentDisconnected', data => {
$("#divRegister").hide();
$('#divGame').hide();
$('#divGameInfo').html(" ");
$('#divInfo').html("<br/><span><strong>Hey " + playerName +
"! Your opponent disconnected or left the battle! You
are the winner ! Hip Hip Hurray!!!</strong></span>");
});
After every move, both players need to be updated by the server about the move made, so that both players' game boards are in sync. So, on the server side we will need an additional model called MoveInformation, which will contain information on the latest move made by the player and the server will send this model to both the clients to keep them in sync:
/// <summary> /// While playing the game, players would make moves. This class contains the information of those moves. /// </summary> internal class MoveInformation { /// <summary> /// Gets or sets the opponent name. /// </summary> public string OpponentName { get; set; }
/// <summary>
/// Gets or sets the player who made the move.
/// </summary>
public string MoveMadeBy { get; set; }
/// <summary>
/// Gets or sets the image position. The position in the game
board (0-8) where the player placed his
/// image.
/// </summary>
public int ImagePosition { get; set; }
/// <summary>
/// Gets or sets the image. The image of the player that he
placed in the board (0-8)
/// </summary>
public string Image { get; set; }
}
Finally, we will wire up the remaining methods in the GameHub class to complete the game coding. The MakeAMove method is called every time a player makes a move. Also, we have overidden the OnDisconnectedAsync method to inform a player when their opponent disconnects. In this method, we also keep our players and games list current. The comments in the code explain the workings of the methods:
/// <summary> /// Invoked by the player to make a move on the board. /// </summary> /// <param name="position">The position to place the player</param> public void MakeAMove(int position) { //// Lets find a game from our list of games where one of the player has the same connection Id as the current connection has. var game = games?.FirstOrDefault(x => x.Player1.ConnectionId == Context.ConnectionId || x.Player2.ConnectionId == Context.ConnectionId);
if (game == null || game.IsOver)
{
//// No such game exist!
return;
}
//// Designate 0 for player 1
int symbol = 0;
if (game.Player2.ConnectionId == Context.ConnectionId)
{
//// Designate 1 for player 2.
symbol = 1;
}
var player = symbol == 0 ? game.Player1 : game.Player2;
if (player.WaitingForMove)
{
return;
}
//// Update both the players that move is made.
Clients.Client(game.Player1.ConnectionId)
.InvokeAsync(Constants.MoveMade, new MoveInformation {
OpponentName = player.Name, ImagePosition = position,
Image = player.Image });
Clients.Client(game.Player2.ConnectionId)
.InvokeAsync(Constants.MoveMade, new MoveInformation {
OpponentName = player.Name, ImagePosition = position,
Image = player.Image });
//// Place the symbol and look for a winner after every
move.
if (game.Play(symbol, position))
{
Remove<Game>(games, game);
Clients.Client(game.Player1.ConnectionId)
.InvokeAsync(Constants.GameOver, $"The winner is
{player.Name}");
Clients.Client(game.Player2.ConnectionId)
.InvokeAsync(Constants.GameOver, $"The winner is
{player.Name}");
player.IsPlaying = false;
player.Opponent.IsPlaying = false;
this.Clients.Client(player.ConnectionId)
.InvokeAsync(Constants.RegistrationComplete);
this.Clients.Client(player.Opponent.ConnectionId)
.InvokeAsync(Constants.RegistrationComplete);
}
//// If no one won and its a tame draw, update the
players that the game is over and let them
look for new game to play.
if (game.IsOver && game.IsDraw)
{
Remove<Game>(games, game);
Clients.Client(game.Player1.ConnectionId)
.InvokeAsync(Constants.GameOver, "Its a tame
draw!!!");
Clients.Client(game.Player2.ConnectionId)
.InvokeAsync(Constants.GameOver, "Its a tame
draw!!!");
player.IsPlaying = false;
player.Opponent.IsPlaying = false;
this.Clients.Client(player.ConnectionId)
.InvokeAsync(Constants.RegistrationComplete);
this.Clients.Client(player.Opponent.ConnectionId)
.InvokeAsync(Constants.RegistrationComplete);
}
if (!game.IsOver)
{
player.WaitingForMove = !player.WaitingForMove;
player.Opponent.WaitingForMove =
!player.Opponent.WaitingForMove;
Clients.Client(player.Opponent.ConnectionId)
.InvokeAsync(Constants.WaitingForOpponent,
player.Opponent.Name);
Clients.Client(player.ConnectionId)
.InvokeAsync(Constants.WaitingForOpponent,
player.Opponent.Name);
}
}
With this, we are done with the coding of the game and are ready to run the game app. So there you have it! You've just built your first game in .NET Core! The detailed source code can be downloaded from Github.
If you're interested in learning more, head on over to get the book, .NET Core 2.0 By Example, by Rishabh Verma and Neha Shrivastava.
Applying Single Responsibility principle from SOLID in .NET Core
Unit Testing in .NET Core with Visual Studio 2017 for better code quality
Get to know ASP.NET Core Web API [Tutorial]