Ludo Web project is a .NET online Ludo Board Game Platform. It was made between 10th of may and 30th of may 2021, during our studies to .NET Developer at Teknik Högskolan.
Albin Alm -> GitHub
Robin Axelsson -> GitHub
Table of content
- Game rooms
- Multiplayer
- AI players
- Email invitations
- SQL database with users, gamedata
- Token and cookie authentication
- Server Side Logic
- ASP.NET
- Razor Pages
- C#
- SignalR
- SQL-Server
- Entity Framework
- Git version control
- GitHub
- JavaScript
- Visual Studio
- SMTP client
With SOLID principles in mind we keep the libraries (LudoGame, LudoTranslation) as libraries - independent of the main projects.
- LudoGame: is the logic and the factories for the game objects.
- LudoDataAccess: Manages the data layer with users and game data.
- LudoTranslation: Manages the language translations in email and web interface.
- LudoWeb: Manages the frontend (Razor pages and JavaScript), the networking of the game and the game rooms (and more).
All the game logic is calculated on the server (to eliminate cheating).
foreach (var player in _orderedPlayers)
{
/*
rolling dice
check board for valid options to provide the player
waits for player response
validates the response
if valid:
update the board in memory
send updated pawns through SignalR
*/
}
}
This image describes the loop with both a human player connected over the network, but also our AI (called Stefan).
Links to classes in the diagram.
A good way to clear up dependencies is a good design for instantiating the Library classes. This is made through stand alone container injection of LudoGame in LudoProvider.cs. This design creates an additional dependency injection system separate from ASP.NETs. The system adds a new container of game services for every created instance of the LudoProvider. This makes it possible to have multiple game rooms with different Ludo games at the same time.
public LudoProvider()
{
var container = new ServiceCollection();
container
.AddSingleton<IBoardCollection, BoardCollection>()
.AddSingleton<IGameAction, GameAction>()
.AddSingleton<IGamePlay, GamePlay>()
.AddSingleton<IGameFunction, GameFunction>()
.AddSingleton<IGameEvent, DefaultGameEvent>()
.AddSingleton<IOptionsValidator, OptionsValidator>()
.AddSingleton<IEqualityComparer<Pawn>, PawnComparer>()
.AddSingleton<IGameFunction, GameFunction>()
.AddSingleton<IBoardOrm, BoardOrm>();
_provider = container.BuildServiceProvider();
}
public T GetGameService<T>()
{
return _provider.GetService<T>();
}
This class provide the needed game services for each game and is applied in GameRoom.cs
public GameRoom(AbstractFactory gameFactory, IGameNetworkManager manager, string gameId, LudoNetworkFactory factory)
{
var provider = gameFactory.BuildLudoProvider();
_gamePlay = provider.GetGameService<IGamePlay>();
_gameEvent = provider.GetGameService<IGameEvent>();
_boardCollection = provider.GetGameService<IBoardCollection>();
Generates a HTML Email body and sends it to clients using SMTP-protocol.
First of all we instantiate a new SMTP client using Google Server, since our host email is using Gmail.
We then wish to get a list of all the account objects connected to the recipients. This is because we want the e-mail to be formatted in the receivers preferred language using TranslationEngine.
We are also sending the emails separately using a for-loop. This is of two reasons:
- We don't want to leak any data to the recipients. Like other receivers.
- We want the e-mail to be translated individually based on the recipients preferred language
Alters strings using reflection and source files
First of all we have .lang files which contains lines of the respective translation and it's correspondent property name. The content is split using double equal signs.
Example: Game_H1Title==Välkommen till Ludo!
Now on the InitializeLanguage() method will firstly make a new instance of the class Dict that contains the properties.
Secondly, a StreamReader will reach each line in the .lang file and find a property with the same name as the left section in that new instance of Dict. If it finds such property it will set that value to it's correspondant left section. Then this process is repeated until the entire file has been read.
As a final result you will get back the newly instanced Dict class and can access all the properties with it's values.
Then just set the chosen content value to any property of Dict.
We chose to structure it this way since it is very simple to extend the translation afterwards.
If we want to add a completely new string, the only thing we gotta do is to create that property in Dict.cs and then add that property and it's value to the language file.
If we instead wish to add another language we just need to name the file a 2-character ISO standard country name(SE, US, RU...) and then .lang.
In that file just copy over the properties and set the desired values.