Duotris
I am so sorry, this article is in Dutch only :p however Google Translate does a pretty good job.
Introductie
Duotris is een spel voor twee spelers waarmee Tetris gespeeld kan worden over internet. Het doel is om je tegenspeler uit te schakelen door zelf zoveel mogelijk lijnen te vereffenen en daarmee het je tegenstander lastig te maken. Deze ontvangt namelijk een gerandomiseerde lijn als je 4 lijnen in 1x weet weg te spelen. Deze variant doet me denken aan vervlogen tijden...
Als onderdeel van een assessment voor een klus heb ik hier een PoC voor gebouwd.
Het is een port van een eerder door mij gebouwd .NET 4.5 project en geporteerd naar .NET Core 2.0. Het één en ander is ontwikkeld onder Visual Studio Code op een Mac en draait ook onder Microsoft Windows.
Inhoud
- Doel van het Proof of Concept
- Over het gebruik van SignalR
- Over de backend-end
- Over de front-end
- Screenshot
- Build en run
- Download
Doel van het Proof of Concept
Het doel van het PoC is tweeledig en bestaat uit het aantonen van de volgende zaken:
- porteren van .NET 4.5 naar .NET Core 2.0;
- toepassen van Dependency Injection.
Andere zaken zoals de front-end vallen buiten het PoC .
Portering
Zoals verwacht vormden externe dependencies het grootste struikelblok. Met name SignalR heeft een andere API gekregen én het was lastig om de juiste versie te vinden; ook omdat het – voor mij – niet onmiddellijk duidelijk was dat .NET Core 2.0 in tegenstelling tot .NET Core 2.1 nog geen SignalR aan boord had. Uiteindelijk heb ik de pre-release SignalR 1.0.0-rc1-final gebruikt. Het is nl. net iets te vroeg om .NET Core 2.1 onder MacOS te draaien.
Daar tegenover staat dat het aantal expliciete dependencies van vijf naar één is teruggelopen. Dit komt grotendeels doordat OWIN onderdeel is geworden van Microsoft.AspNetCore.All. Met de komst van .NET Core 2.1 zal dit aantal zelfs teruglopen tot nul omdat SignalR dan ook onderdeel van Microsoft.AspNetCore.All is geworden. Hou je ogen op je mailbox voor een update!
Update!
We schrijven inmiddels begin 2019. De beloofde portering heeft plaatsgevonden. Minder dependencies, minder zorgen. Het project zelf is van dotnetcore2.0
naar dotnetcore2.2
geüpgraded. Microsoft.AspNetCore.SignalR hoeft niet meer apart gerefereerd te worden. Ook is het minder uitgebreide Microsoft.AspNetCore.App i.p.v. Microsoft.AspNetCore.All gebruikt. De browser client is geüpgraded naar SignalR 1.1.0.
Update to .NET 7.0
Apart from a [SignalR issue](https://github.com/dotnet/aspnetcore/issues/48690), it was pretty easy to upgrade.
Dependency Injection
Voor een dergelijke kleine applicatie is het eigenlijk overkill om DI toe te passen. Het ontwerp van SignalR maakt het echter wenselijk om toch DI te hanteren.
De SignalR hubs worden nl. per request geïnstantieerd. Hierdoor is het verleidelijk om de game state als static members in de SignalR hub op te nemen of zelfs globaal te declareren. Niet goed. Dit schendt natuurlijk werkelijk elk concept van S.O.L.I.D. op meerdere manieren!
Ik heb er voor gekozen om de classes GameList
en PlayerQueue
in DuotrisHub
te injecteren als singletons (wah!) om dit op te lossen. Een refactorslag die nog zou kunnen plaatsvinden is het losser koppelen van DuotrisHub en de spellogica (methods QueueForMatch()
, Move()
, GameOver()
etc.). Bijvoorbeeld door het mediator pattern toe te passen. Ook het splitsen van beweeg- en spellogica is een optie. Dit valt buiten de scope van het PoC.
services.AddSingleton<IGameList, GameList>(); services.AddSingleton<IPlayerQueue, PlayerQueue>();
Over het gebruik van SignalR
Er is gebruik gemaakt van SignalR om real-time communicatie te laten plaatsvinden tussen de game server en (browser-)clients. De spelers worden gepaard voor het spelbegin. Hiertoe worden er op de server twee lijsten bijgehouden; de eerdergenoemde game state.
- wachtende spelers; PlayerQueue
- lopende spellen; GameList
Als er genoeg wachtende spelers zijn (nl. twee stuks) worden deze van de wachtende spelers naar de lopende spellen verplaatst en begint het spel.
Opmerking: er had gebruik gemaakt kunnen worden van SignalR groups om spelers bij elkaar te houden, maar het groepenconcept in SignalR kent wat beperkingen; het is mogelijk om spelers aan een groep toe te voegen of te verwijderen. Het opvragen van spelers in een groep of zelfs het aantal spelers bepalen is echter niet mogelijk.
SignalR in de back-end
Het configureren van SignarlR is een breeze in ASP.NET Core.
app.UseSignalR(routes => { routes.MapHub<DuotrisHub>("/duotrishub"); });
Over de back-end
In de klasse DuotrisHub is de spellogica te vinden. Enkele relevante snippets:
private void QueueForMatch(string player)
{
lock (playerQueue)
{
Console.WriteLine($"Player {player} is waiting for a game.");
playerQueue.Enqueue(player);
}
}
private void TryMakeMatch()
{
lock (playerQueue)
{
if (playerQueue.Count >= 2)
{
string player1 = playerQueue.Dequeue();
string player2 = playerQueue.Dequeue();
Console.WriteLine($"Starting a match for player {player1} and {player2}.");
// add players to a game and notify them
matches.Add(player1, player2);
Clients.Client(player1).SendAsync("Start");
Clients.Client(player2).SendAsync("Start");
Clients.Client(player1).SendAsync("Message", "Game started!");
Clients.Client(player2).SendAsync("Message", "Game started!");
}
}
}
Over de front-end
De front-end is gebouwd in plain JavaScript en een snelle hack bovenop JavaScript Tetris in 1.5kB( (2004). Don't look at it too closely :-)
Andere keer... een TypeScript-versie komt er aanis beschikbaar!
SignalR in de front-end
Het initialiseren van SignalR in de front-end werkt als volgt:
const connection = new signalR.HubConnectionBuilder() .withUrl('/duotrishub') .build() // ... connection.on('move', (bucket, score, lines) => { // ... } connection.start()
Vervolgens kunnen er boodschappen naar de SignalR Hub verstuurd worden.
connection.send('Move', bucket, score, lines)
Screenshot
Build en run
Voor het bouwen en runnen is .NET Core 2.2 vereist.
Gebruik het volgende commando op een command-line om de applicatie te bouwen en te starten:
dotnet run
Navigeer vervolgens in twee aparte browser-vensters naar localhost:5000 om een spel te starten.
Download
Git repository: duotris.