-
Notifications
You must be signed in to change notification settings - Fork 3
Project structuur

In de solution vind je 9 projecten:
- Battleship.Api -> Dit project is de eigenlijke webapplicatie. In de Controller folder vind je enkele classes die methoden bevatten die uitgevoerd worden als er een http request binnenkomt op een bepaalde url. Om de minimale vereisten te implementeren is het niet nodig om hier 1 letter code te wijzigen. Het gaat wel handig zijn om hier breakpoints te plaatsen om je applicatie te debuggen.
- Battleship.Api.Tests -> Bevat automatische testen voor de Controller classes in het Battleship.Api project. Deze testen zijn initieel allemaal groen. Aan jullie om dit zo te houden!
- Battleship.Business -> Bevat enkele service classes die gebruikt worden door de Controller classes in het Battleship.Api project. Hierin zal je enkele gaten moeten invullen.
- Battleship.Business.tests -> Bevat automatische testen voor de classes in het Battleship.Business project. Gebruik (naast de documentatie) deze testen om te achterhalen hoe je deze classes het best implementeert.
- Battleship.Data -> Bevat logica die toelaat om data op te slaan in een database of in geheugen. Voor deze applicatie worden gebruikers (spelers) opgeslagen in een database. Hiervoor moet je geen code schrijven. Ook is er een Repository class waarmee de lopende games in geheugen bijgehouden worden. Je hoeft geen code te wijzigen in deze class, maar het is niet slecht om de interne werking van InMemoryGameRepository eens te bestuderen.
- Battleship.Data.tests -> Bevat automatische testen voor de classes in het Battleship.Data project. Jij moet enkel ervoor zorgen dat deze testen groen blijven.
- Battleship.Domain -> Bevat de domain classes. Deze classes proberen de logica van een zeeslag-spel te capteren. Hier zitten de grootste gaten die je zal moeten opvullen.
- Battleship.Domain.Tests -> Bevat automatische testen voor de classes in het Battleship.Domain project. Gebruik (naast de documentatie) deze testen om te achterhalen hoe je deze classes het best implementeert.
- Battleship.TestTools -> Bevat enkele classes die gebruikt worden door de testprojecten. Dit project mag je volledig negeren.
Als je de applicatie start, dan word je automatisch geleid naar https://localhost:5001/swagger/index.html.
Hier vind je een uitleg over de verschillende http requesten die je kan sturen naar endpoints van de backend. (Lees eerst even de documentatie over client-server architectuur als je nog niet weet wat er met endpoints bedoeld wordt.) Je kan hier voor elke request zien welke parameters (json) er verwacht worden en welke responses je mag verwachten. Je kan zelfs de API aanspreken via deze user interface.

Wat misschien minder duidelijk is, is welke code er uitgevoerd wordt als er een http request aankomt op een eindpoint van de applicatie. Hieronder vind je een overzicht van de methodes die worden uitgevoerd:
| Http request | C# methode | C# class | Omschrijving |
|---|---|---|---|
| POST https://localhost:5001/api/authentication/register | Register | AuthenticationController | Nieuwe gebruiker registreren |
| POST https://localhost:5001/api/authentication/token | CreateToken | AuthenticationController | Een bearer token vragen |
| POST https://localhost:5001/api/games | CreateNewSinglePlayerGame | GamesController | Een nieuw spel aanmaken |
| POST https://localhost:5001/api/games{id}/positionship | PositionShipOnGrid | GamesController | Een ship plaatsen op je grid |
| POST https://localhost:5001/api/games/{id}/start | StartGame | GamesController | Een spel starten nadat de schepen geplaatst zijn |
| GET https://localhost:5001/api/games/{id} | GetGameInfo | GamesController | Informatie over het huidige spel opvragen (= json versie van een Battleship.Business.Models.GameInfo object) |
| POST https://localhost:5001/api/games{id}/shoot | ShootAtOpponent | GamesController | Een bom schieten naar de tegenstander |
| GET https://localhost:5001/api/home/ping | Ping | HomeController | Endpoint om te controleren of de applicatie nog draait |
De betekenis van elk end point wordt meer toegelicht in de commentaren die in de code staan. Je hoeft (voor de minimale vereisten) geen code te wijzigen in dit project. Wel is het aan te raden om breakpoints te plaatsen in de methodes in bovenstaande tabel om zo all debuggend de flow van de code te analyseren.
Voor de meeste end points moet de gebruiker zich authenticeren door een (bearer) token mee te sturen in de Authorization header van de http request. Dit token kan je verkrijgen via het end point https://localhost:5001/api/authentication/token. (dit kan je dus eventueel opvragen bij login)
De http header ziet er als volgt uit:
Authorization: Bearer {theToken}
In de Services folder vind je de class GameService. Dit is de class die door de controller classes in het Api project gebruikt wordt om bewerkingen rond een spel (Game) te doen. De class coordineert de logica rond:
- het aanmaken van een Game en deze opslaan in het geheugen
- het starten van een Game
- het ophalen van de toestand van een Game gezien door de ogen van een Player
- het positioneren van schepen
- het schieten naar de tegenstander
De GameService class coördineert. Dat wil zeggen dat hij veel verantwoordelijkheid doorschuift naar andere classes (in het Battleship.Data project en Battleship.Domain project). De GameService doet alles met 3 instanties van objecten die worden doorgegeven via de constructor:
- een instantie van een class die IGameFactory implementeert. Hiermee kan een Game kan aangemaakt worden. De concrete instantie zal van het type Battleship.Domain.GameDomain.GameFactory zijn.
- een instantie van een class die IGameRepository implementeert. Hiermee kan een nieuw spel opgeslagen worden in het geheugen en terug opgehaalt worden. De concrete instantie zal van het type Battleship.Data.Repositories.InMemoryGameRepository zijn.
- een instantie van een class die IGameInfoFactory implementeert. Hiermee kan je de informatie over een Game (GameInfo) in de ogen van een Player construeren. Een player mag niet alle informatie van een Game te zien krijgen (bijvoorbeeld de posities van de schepen van de tegenstander). Daarom wordt een Game eerst omgezet naar een GameInfo object vooraleer we het over het netwerk naar de speler sturen.
De gegeven code zorgt er al voor dat de juiste instanties van de juiste classes doorgegeven worden aan de constructor. Jij moet deze instanties nu nog correct gebruiken. Laat je leiden door de automatische testen...
In de Models folder vind je de GameInfo class die gegevens over een Game bevat door de bril van een Player. GameInfo bevat de volgende informatie:
- Id: unieke identifier (Guid) van de Game. Zoek zelf eens op wat een Guid betekent en hoe je er mee werkt in C#.
- IsReadyToStart: geeft aan of er gestart kan worden met schieten.
- HasBombsLoaded: geeft aan of de bom(men) van de Player geladen zijn. Indien ja, dan kan hij schieten. Indien nee, dan is de tegenstander aan de beurt.
- OwnGrid: informatie over de Grid van de Player.
- OwnShips: informatie over de schepen van de Player.
- OpponentGrid: informatie over de Grid van de tegenstander.
- SunkenOpponentShips: een lijst van de gezonken schepen van de tegenstander.
Aan de classes GameInfo, GridInfo en ShipInfo hoef je niets meer te doen. Waar je wel nog aan moet werken zijn de bijhorende Factory classes: GameInfoFactory, GridInfoFactory en ShipInfoFactory. De factory classes bevatten de logica die nodig is om informatie over een Game om te zetten naar informatie die een speler mag zien. Laat je leiden door de automatische testen...
In dit project hoef je geen code te wijzigen, maar het is wel aangeraden om de class InMemoryGameRepository in de Repositories folder eens te bestuderen. In de InMemoryGameRepository kan je zien hoe gedurende de levensduur van de web applicatie de verschillende games in geheugen worden bijgehouden.
De geregistreerde gebruikers worden in een database opgeslagen. Deze database kan je als volgt vinden:
- Open het venster Sql Server Object Explorer in Visual Studio

- Maak een connectie met de lokale database server:
- Klik op het +-icoon
- Open Local
- Kies voor MSSQLLocalDb
- Klik op Connect zonder iets te wijzigen

- Bij het starten van de Api wordt er automatisch een database BattleshipDb aangemaakt. Deze zou je moeten terugvinden onder (localdb)\MSSQLLocalDB -> Databases.
Om de gebruikers te bekijken die in de databank zitten doe je het volgende:
- Ga naar BattleshipDb -> Tables
- Klik rechts op dbo.AspNetUsers
- Klik op View Data

Als je met een schone lei terug wil starten dan kan je de database wissen en opnieuw aanmaken als volgt:
- Klik rechts op BattleshipDb
- Klik op Delete
- Vink Close existing connections aan en klik op Ok
- Door de Api nu opnieuw te starten zal er automatisch een nieuwe lege databank aangemaakt worden.
De classes in dit project proberen de logica van een zeeslag-spel te capteren. Het gaat hier om een heel aantal klassen. Daarom zijn ze ingedeeld in 4 subdomeinen:
- GameDomain: classes die een zeeslag spel helpen modeleren (Game, GameFactory, GameMode, GameSettings)
- PlayerDomain: classes die een speler modeleren (HumanPlayer, ComputerPlayer, RandomShootingStrategy)
- GridDomain: classes die een 10x10 grid modeleren (Grid, GridCoordinate, GridSquare, GridSquareStatus)
- FleetDomain: classes die de vloot van schepen van een speler modeleren (Fleet, Ship, ShipKind)
Er zijn ook enkele hulp classes die niet tot een specifiek domein horen:
- User: een (menselijke) geregistreerde gebruiker. Je hoeft niets te wijzigen in deze code.
- DataNotFoundException: exceptie die gegooit wordt als er iets niet gevonden kan worden (bijvoorbeeld een Game met een bepaalde id.)
- Direction: een class waarmee je vanalles kan doen met richtingen (noord, oost, zuid, west). Deze class krijg je volledig cadeau en zal zeker van pas komen (bijvoorbeeld bij het willekeurig plaatsen van de schepen van een computerspeler). Bestudeer deze class dan ook goed zodat je weet wanneer je hem zou kunnen gebruiken.
- Result: een class waarmee je een resultaat kan terug geven die een reden bevat als het is mis gegegaan. Ook deze code krijg je cadeau.
- ShotResult: een class die het resultaat bevat van een shot op de Grid van de tegenstander. Ook deze code krijg je cadeau.
In het domain project zijn veel classes aanwezig. Waar moet je nu beginnen?
- Werk feature per feature. Laat ons hier het voorbeeld "Een nieuw spel starten" nemen.
- Traceer de flow van de code vanuit het API project. Plaats een breakpoint in de juiste controller methode en gebruik debugging technieken om dieper en dieper in de code te stappen. Voor het starten van een nieuw spel plaats je een breakpoint in de CreateNewSinglePlayerGame methode.
- Vervolledig de methodes / properties die je tegen komt tijdens het debuggen. Laat je leiden door de automatische testen. Methodes en properties die je niet nodig hebt voor de huidige feature laat je voorlopig links liggen. Bij het aanmaken van een nieuw spel zal je de CreateGameForUser methode van de GameService moeten implementeren. Vervolgens zal je merken dat je de CreateNewSinglePlayerGame methode van GameFactory zal moeten implementeren en de CreateFromGame methode van de GameInfoFactory class, enzovoort.
- Als de automatische testen aangeven dat een bepaalde methode / property van een andere class nog niet geïmplementeerd is dan implementeer je die eerst.
-
Zorg dat je de gegeven code ook begrijpt. Neem de tijd om de achterliggende theorie te bestuderen. Als je weet wat je aan het doen bent dan zal je code veel beter zijn en zal het ook aangenamer zijn om te programmeren.
- Als er in een class parameter van een interface type wordt doorgegeven in een constructor, dan ga je ook de class die de interface implementeert moeten aanvullen om de feature aan het werken te krijgen. Tip: als je rechts klikt op een interface type en je selecteert Go to implementation dan kom je uit op de class die de interface implementeert.
Om het plaatsen van schepen te vereenvoudigen maak je gebruik van extensies op een array van grid coördinaten (GridCoordinate[]). Deze extensies moet je zelf nog invullen in de GridCoordinateArrayExtensions class. Om goed te weten wat je in deze class moet doen en hoe je de methoden in deze class kan gebruiken, ga je eens moeten onderzoeken wat extension methods in C# zijn. Op https://www.tutorialsteacher.com/csharp/csharp-extension-method vind je een vrij heldere uitleg, maar niets houd je tegen om ook andere bronnen te raadplegen.
Als een computerspeler aan zet is, dan moet hij bepalen op welk vak van de tegenstander hij gaat schieten. Dit doe je door een class te maken die de interface IShootingStrategy implementeert. Voor de minimale vereisten is dat de class RandomShootingStrategy.
Bij de RandomShootingStrategy wordt er geschoten op een willekeurig vak van de tegenstander. Er wordt enkel geschoten op vakken waarop nog niet geschoten was. Met het resultaat (ship geraakt of niet?) wordt in deze strategie geen rekening gehouden.
Wil je een slimmere computerspeler bouwen (als extra) dan implementeer je de class SmartShootingStrategy. Deze strategie onthoudt wanneer er een schip geraakt wordt en gaat proberen dat schip snel te zinken door de volgende keer in de buurt te schieten. De strategie is ook slim genoeg om te weten in welke richting een schip (waarschijnlijk) ligt als er 2 geraakte vakjes naast elkaar liggen.
In de GameFactory class kan je bepalen welke shooting strategy je meegeeft aan een computerspeler.