diff --git a/BackendTests/BackendTests.csproj b/BackendTests/BackendTests.csproj index f95e2a7..98fa5fa 100644 --- a/BackendTests/BackendTests.csproj +++ b/BackendTests/BackendTests.csproj @@ -13,9 +13,9 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/BackendTests/Controllers/GameControllerTests.cs b/BackendTests/Controllers/GameControllerTests.cs index 3e236cb..1f24f3a 100644 --- a/BackendTests/Controllers/GameControllerTests.cs +++ b/BackendTests/Controllers/GameControllerTests.cs @@ -11,12 +11,10 @@ using pacMan.Utils; namespace BackendTests.Controllers; +[TestFixture] +[TestOf(nameof(GameController))] public class GameControllerTests { - private IActionService _actionService = null!; - private GameController _controller = null!; - private GameService _gameService = null!; - [SetUp] public void Setup() { @@ -27,6 +25,10 @@ public class GameControllerTests _controller = new GameController(Substitute.For>(), _gameService, _actionService); } + private IActionService _actionService = null!; + private GameController _controller = null!; + private GameService _gameService = null!; + [Test] public void Run_ReturnsSame() { @@ -52,8 +54,6 @@ public class GameControllerTests Assert.Fail("Result is not an ArraySegment"); } - #region DoAction(ActionMessage message) - [Test] public void DoAction_NegativeAction() { @@ -71,6 +71,4 @@ public class GameControllerTests _controller.DoAction(message); Assert.That(message.Data, Is.EqualTo(data)); } - - #endregion } diff --git a/BackendTests/Controllers/PlayerControllerTests.cs b/BackendTests/Controllers/PlayerControllerTests.cs index 8e4cc3b..9c40686 100644 --- a/BackendTests/Controllers/PlayerControllerTests.cs +++ b/BackendTests/Controllers/PlayerControllerTests.cs @@ -1,5 +1,9 @@ +using pacMan.Controllers; + namespace BackendTests.Controllers; +[TestFixture] +[TestOf(nameof(PlayerController))] public class PlayerControllerTests { // TODO diff --git a/BackendTests/Game/Items/DiceCupTests.cs b/BackendTests/Game/Items/DiceCupTests.cs index 2c691d7..3fa27e5 100644 --- a/BackendTests/Game/Items/DiceCupTests.cs +++ b/BackendTests/Game/Items/DiceCupTests.cs @@ -2,6 +2,8 @@ using pacMan.GameStuff.Items; namespace BackendTests.Game.Items; +[TestFixture] +[TestOf(nameof(DiceCup))] public class DiceCupTests { [Test] diff --git a/BackendTests/Game/Items/DiceTests.cs b/BackendTests/Game/Items/DiceTests.cs index 9d2313f..bd1cfcf 100644 --- a/BackendTests/Game/Items/DiceTests.cs +++ b/BackendTests/Game/Items/DiceTests.cs @@ -2,6 +2,8 @@ using pacMan.GameStuff.Items; namespace BackendTests.Game.Items; +[TestFixture] +[TestOf(nameof(Dice))] public class DiceTests { [Test] diff --git a/BackendTests/Services/ActionServiceTests.cs b/BackendTests/Services/ActionServiceTests.cs index 1c8a791..258fe95 100644 --- a/BackendTests/Services/ActionServiceTests.cs +++ b/BackendTests/Services/ActionServiceTests.cs @@ -10,21 +10,10 @@ using pacMan.Services; namespace BackendTests.Services; +[TestFixture] +[TestOf(nameof(ActionService))] public class ActionServiceTests { - private readonly Player _blackPlayer = Players.Create("black"); - private readonly Player _redPlayer = Players.Create("red"); - private readonly Player _whitePlayer = Players.Create("white"); - private ActionMessage _blackMessage = null!; - private pacMan.Services.Game _game = null!; - private GameService _gameService = null!; - private ActionMessage _redMessage = null!; - private IActionService _service = null!; - - private Queue _spawns = null!; - private ActionMessage _whiteMessage = null!; - - [SetUp] public void Setup() { @@ -37,6 +26,18 @@ public class ActionServiceTests _service = new ActionService(Substitute.For>(), _gameService); } + private readonly Player _blackPlayer = Players.Create("black"); + private readonly Player _redPlayer = Players.Create("red"); + private readonly Player _whitePlayer = Players.Create("white"); + private ActionMessage _blackMessage = null!; + private pacMan.Services.Game _game = null!; + private GameService _gameService = null!; + private ActionMessage _redMessage = null!; + private IActionService _service = null!; + + private Queue _spawns = null!; + private ActionMessage _whiteMessage = null!; + private JsonElement SerializeData(string username) => JsonDocument.Parse(JsonSerializer.Serialize( new JoinGameData { Username = username, GameId = _game.Id } @@ -51,8 +52,6 @@ public class ActionServiceTests new() { At = new Position { X = 9, Y = 9 }, Direction = Direction.Right } }); - #region RollDice() - [Test] public void RollDice_ReturnsListOfIntegers() { @@ -65,10 +64,6 @@ public class ActionServiceTests }); } - #endregion - - #region PlayerInfo(ActionMessage message) - [Test] public void PlayerInfo_DataIsNull() { @@ -98,10 +93,6 @@ public class ActionServiceTests Assert.That(new List { _whitePlayer }, Is.EqualTo(players)); } - #endregion - - #region Ready() - [Test] public void Ready_PlayerIsNull() { @@ -162,10 +153,6 @@ public class ActionServiceTests Is.EqualTo(_blackPlayer.Username).Or.EqualTo(_whitePlayer.Username)); } - #endregion - - #region FindNextPlayer() - [Test] public void FindNextPlayer_NoPlayers() { @@ -200,6 +187,4 @@ public class ActionServiceTests var second = _service.FindNextPlayer(); Assert.That(second, Is.EqualTo(_whitePlayer.Username)); } - - #endregion } diff --git a/BackendTests/Services/GameServiceTests.cs b/BackendTests/Services/GameServiceTests.cs index 59e9df9..7905c9d 100644 --- a/BackendTests/Services/GameServiceTests.cs +++ b/BackendTests/Services/GameServiceTests.cs @@ -3,19 +3,14 @@ using Microsoft.Extensions.Logging; using NSubstitute; using pacMan.Exceptions; using pacMan.GameStuff; -using pacMan.GameStuff.Items; using pacMan.Services; namespace BackendTests.Services; +[TestFixture] +[TestOf(nameof(GameService))] public class GameServiceTests { - private readonly DirectionalPosition _spawn3By3Up = new() - { At = new Position { X = 3, Y = 3 }, Direction = Direction.Up }; - - private GameService _service = null!; - private Queue _spawns = null!; - [SetUp] public void SetUp() { @@ -29,7 +24,11 @@ public class GameServiceTests }); } - #region CreateAndJoin(IPlayer player, Queue spawns) + private readonly DirectionalPosition _spawn3By3Up = new() + { At = new Position { X = 3, Y = 3 }, Direction = Direction.Up }; + + private GameService _service = null!; + private Queue _spawns = null!; [Test] public void CreateAndJoin_WhenEmpty() @@ -53,15 +52,11 @@ public class GameServiceTests Assert.Throws(() => _service.CreateAndJoin(player, _spawns)); } - #endregion - - #region JoinbyId(Guid id) - [Test] public void JoinById_WhenIdNotExists() { var player = Players.Create("white"); - _service.Games.Add(new pacMan.Services.Game(_spawns) { Players = new List { player } }); + _service.Games.Add(new pacMan.Services.Game(_spawns) { Players = [player] }); Assert.Throws(() => _service.JoinById(Guid.NewGuid(), player)); } @@ -70,7 +65,7 @@ public class GameServiceTests public void JoinById_WhenIdExists() { var player = Players.Create("white"); - var game = new pacMan.Services.Game(_spawns) { Players = new List { player } }; + var game = new pacMan.Services.Game(_spawns) { Players = [player] }; _service.Games.Add(game); @@ -84,6 +79,4 @@ public class GameServiceTests Assert.That(_service.Games, Has.Count.EqualTo(1)); }); } - - #endregion } diff --git a/BackendTests/Services/GameTests.cs b/BackendTests/Services/GameTests.cs index fb0822f..444dc4f 100644 --- a/BackendTests/Services/GameTests.cs +++ b/BackendTests/Services/GameTests.cs @@ -6,8 +6,24 @@ using pacMan.Utils; namespace BackendTests.Services; +[TestFixture] +[TestOf(nameof(pacMan.Services.Game))] public class GameTests { + [SetUp] + public void Setup() + { + _spawns = new Queue( + new[] { _spawn3By3Up, _spawn7By7Left, _spawn7By7Down, _spawn7By7Right }); + + _game = new pacMan.Services.Game(_spawns); + _redPlayer = Players.Create("red"); + _bluePlayer = Players.Create("blue"); + _yellowPlayer = Players.Create("yellow"); + _greenPlayer = Players.Create("green"); + _purplePlayer = Players.Create("purple"); + } + private readonly DirectionalPosition _spawn3By3Up = new() { At = new Position { X = 3, Y = 3 }, Direction = Direction.Up }; @@ -29,20 +45,6 @@ public class GameTests private Queue _spawns = null!; private Player _yellowPlayer = null!; - [SetUp] - public void Setup() - { - _spawns = new Queue( - new[] { _spawn3By3Up, _spawn7By7Left, _spawn7By7Down, _spawn7By7Right }); - - _game = new pacMan.Services.Game(_spawns); - _redPlayer = Players.Create("red"); - _bluePlayer = Players.Create("blue"); - _yellowPlayer = Players.Create("yellow"); - _greenPlayer = Players.Create("green"); - _purplePlayer = Players.Create("purple"); - } - private void AddFullParty() { _game.AddPlayer(_bluePlayer); @@ -51,18 +53,12 @@ public class GameTests _game.AddPlayer(_greenPlayer); } - #region NextPlayer() - [Test] public void NextPlayer_WhenEmpty() { Assert.Throws(() => _game.NextPlayer()); } - #endregion - - #region IsGameStarted - [Test] public void IsGameStarted_WhenEmpty() { @@ -101,10 +97,6 @@ public class GameTests Assert.That(_game.IsGameStarted, Is.True); } - #endregion - - #region AddPlayer(Player player) - [Test] public void AddPlayer_WhenEmpty() { @@ -157,10 +149,6 @@ public class GameTests Assert.Throws(() => _game.AddPlayer(_greenPlayer)); } - #endregion - - #region Sendtoall(ArraySegment segment) - [Test] public void SendToAll_WhenConnectionsIsNull() { @@ -171,7 +159,6 @@ public class GameTests public void SendToAll_WhenConnectionsIsNotNull() { var counter = 0; - async Task Send(ArraySegment segment) => await Task.Run(() => counter++); _game.Connections += Send; _game.Connections += Send; @@ -182,12 +169,11 @@ public class GameTests while (counter < 2) { } Assert.That(counter, Is.EqualTo(2)); + return; + + async Task Send(ArraySegment segment) => await Task.Run(() => counter++); } - #endregion - - #region SetReady(Player player) - [Test] public void SetReady_ReturnsAllPlayers() { @@ -222,10 +208,6 @@ public class GameTests Assert.Throws(() => _game.SetReady(_redPlayer.Username)); } - #endregion - - #region SetAllIngame() - [Test] public void SetAllInGame_SetsStateToInGame() { @@ -257,10 +239,6 @@ public class GameTests Assert.That(_game.Players, Is.Empty); } - #endregion - - #region IsGameStarted() - [Test] public void IsGameStarted_AllWaiting() { @@ -275,6 +253,4 @@ public class GameTests _game.Players.ForEach(player => player.State = State.InGame); Assert.That(_game.IsGameStarted, Is.True); } - - #endregion } diff --git a/BackendTests/Services/WebSocketServiceTests.cs b/BackendTests/Services/WebSocketServiceTests.cs index 5a712f6..94ff43a 100644 --- a/BackendTests/Services/WebSocketServiceTests.cs +++ b/BackendTests/Services/WebSocketServiceTests.cs @@ -6,17 +6,17 @@ using pacMan.Utils; namespace BackendTests.Services; +[TestFixture] +[TestOf(nameof(WebSocketService))] public class WebSocketServiceTests { - private IWebSocketService _service = null!; - [SetUp] public void SetUp() { _service = new WebSocketService(Substitute.For>()); } - #region Send(Websocket, ArraySegment) + private IWebSocketService _service = null!; [Test] public void Send_OpenWebsocket() @@ -47,10 +47,6 @@ public class WebSocketServiceTests webSocket.Received().SendAsync(segment, WebSocketMessageType.Text, true, CancellationToken.None); } - #endregion - - #region Receive(Websocket, byte[]) - [Test] public void Receive_ExactBuffer() { @@ -89,10 +85,6 @@ public class WebSocketServiceTests webSocket.ReceivedWithAnyArgs().ReceiveAsync(default, CancellationToken.None); } - #endregion - - #region Close(Websocket, WebSocketCloseStatus, string?) - [Test] public void Close_OpenWebsocket() { @@ -123,6 +115,4 @@ public class WebSocketServiceTests webSocket.ReceivedWithAnyArgs().CloseAsync(default, default, CancellationToken.None); } - - #endregion } diff --git a/BackendTests/Utils/ExtensionsTests.cs b/BackendTests/Utils/ExtensionsTests.cs index 2d6f2bb..1077abc 100644 --- a/BackendTests/Utils/ExtensionsTests.cs +++ b/BackendTests/Utils/ExtensionsTests.cs @@ -3,9 +3,15 @@ using pacMan.Utils; namespace BackendTests.Utils; +[TestFixture] +[TestOf(nameof(Extensions))] public class ExtensionsTests { - #region ToArraySegment(this object obj) + [SetUp] + public void Setup() + { + _bytes = "Hello World!"u8.ToArray(); + } [Test] public void ToArraySegmentValidObject() @@ -24,18 +30,8 @@ public class ExtensionsTests Assert.That(segment, Has.Count.EqualTo(4)); } - #endregion - - #region GetString(this byte[] bytes, int length) - private byte[] _bytes = null!; - [SetUp] - public void Setup() - { - _bytes = "Hello World!"u8.ToArray(); - } - [Test] public void GetString_ValidByteArray() { @@ -65,6 +61,4 @@ public class ExtensionsTests { Assert.That(_bytes.GetString(_bytes.Length / 2), Is.EqualTo("Hello ")); } - - #endregion } diff --git a/DataAccessLayer/Database/Service/UserService.cs b/DataAccessLayer/Database/Service/UserService.cs index 261da69..f63ea48 100644 --- a/DataAccessLayer/Database/Service/UserService.cs +++ b/DataAccessLayer/Database/Service/UserService.cs @@ -4,8 +4,8 @@ namespace DAL.Database.Service; public class UserService { - private readonly List _users = new() - { + private readonly List _users = + [ new User { Username = "Firefox", @@ -18,10 +18,10 @@ public class UserService Password = "Chrome", Colour = "blue" } - }; + ]; - public async Task Login(string username, string password) + public Task Login(string username, string password) { - return await Task.Run(() => _users.FirstOrDefault(x => x.Username == username && x.Password == password)); + return Task.Run(() => _users.FirstOrDefault(x => x.Username == username && x.Password == password)); } } diff --git a/pac-man-board-game.sln.DotSettings.user b/pac-man-board-game.sln.DotSettings.user index cf8098b..cefe584 100644 --- a/pac-man-board-game.sln.DotSettings.user +++ b/pac-man-board-game.sln.DotSettings.user @@ -1,4 +1,7 @@  + True + 35336347-32EB-4764-A28E-3F8FF6CA54C4 + db4927dd-2e12-48a7-9a84-2b7e3e31b9c8 <SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from &lt;BackendTests&gt;" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> <Project Location="/home/martin/Git/Csharp/pac-man-board-game/BackendTests" Presentation="&lt;BackendTests&gt;" /> </SessionState> \ No newline at end of file diff --git a/pac-man-board-game/ClientApp/src/components/gameComponent.tsx b/pac-man-board-game/ClientApp/src/components/gameComponent.tsx index 37258d0..4679f25 100644 --- a/pac-man-board-game/ClientApp/src/components/gameComponent.tsx +++ b/pac-man-board-game/ClientApp/src/components/gameComponent.tsx @@ -14,6 +14,14 @@ import { getData } from "../utils/api" const wsService = new WebSocketService(import.meta.env.VITE_API_WS) +/** + * Represents the main game component. + * @component + * @param player - The current player. + * @param map - The current game map. + * + * @returns The rendered game component. + */ export const GameComponent: FC<{ player: Player; map: GameMap }> = ({ player, map }) => { const players = useAtomValue(playersAtom) const dice = useAtomValue(diceAtom) diff --git a/pac-man-board-game/ClientApp/src/components/gameTile.tsx b/pac-man-board-game/ClientApp/src/components/gameTile.tsx index 8e6a2c1..0f7f1fd 100644 --- a/pac-man-board-game/ClientApp/src/components/gameTile.tsx +++ b/pac-man-board-game/ClientApp/src/components/gameTile.tsx @@ -28,7 +28,7 @@ export const GameTile: FC = ({ showPath = false, }) => ( handleMoveCharacter?.(possiblePath) : undefined} onMouseEnter={possiblePath ? () => handleStartShowPath?.(possiblePath) : undefined} diff --git a/pac-man-board-game/ClientApp/src/game/player.ts b/pac-man-board-game/ClientApp/src/game/player.ts index 0c37c09..1a6e98c 100644 --- a/pac-man-board-game/ClientApp/src/game/player.ts +++ b/pac-man-board-game/ClientApp/src/game/player.ts @@ -4,6 +4,11 @@ import { getDefaultStore } from "jotai" import { currentPlayerNameAtom, playersAtom } from "../utils/state" import rules from "./rules" +/** + * Represents the different states of a game. + * + * @enum {number} + */ export enum State { waitingForPlayers, ready, diff --git a/pac-man-board-game/ClientApp/src/utils/api.ts b/pac-man-board-game/ClientApp/src/utils/api.ts index 25eaa4d..7aa8d6d 100644 --- a/pac-man-board-game/ClientApp/src/utils/api.ts +++ b/pac-man-board-game/ClientApp/src/utils/api.ts @@ -1,3 +1,11 @@ +/** + * getData is an asynchronous function that makes an API request to retrieve data. + * If the mode is test, it returns a promise that resolves to an empty array. + * + * @param path - The path of the API endpoint. + * @param headers - The headers to be included in the request. + * @returns - A promise that resolves to the response from the API. + */ export const getData: Api = async (path, { headers } = {}) => { if (import.meta.env.MODE === "test") return Promise.resolve(new Response(JSON.stringify([]))) return await fetch(import.meta.env.VITE_API_HTTP + path, { @@ -6,6 +14,14 @@ export const getData: Api = async (path, { headers } = {}) => { }) } +/** + * Makes a POST request to the API endpoint. + * + * @param path - The path of the endpoint. + * @param body - The payload of the request. + * @param headers - Additional headers for the request. + * @returns - A Promise that resolves to the Response object representing the server's response. + */ export const postData: Api = async (path, { body, headers } = {}) => { return await fetch(import.meta.env.VITE_API_HTTP + path, { method: "POST", diff --git a/pac-man-board-game/Controllers/GameController.cs b/pac-man-board-game/Controllers/GameController.cs index 457700f..522f2b6 100644 --- a/pac-man-board-game/Controllers/GameController.cs +++ b/pac-man-board-game/Controllers/GameController.cs @@ -9,14 +9,23 @@ using pacMan.Utils; namespace pacMan.Controllers; +/// +/// Controls the game logic and handles requests related to games. +/// [ApiController] [Route("api/[controller]")] public class GameController(ILogger logger, IGameService webSocketService, IActionService actionService) : GenericController(logger, webSocketService) { [HttpGet("[action]")] - public override async Task Connect() => await base.Connect(); + public override Task Connect() => base.Connect(); + /// + /// Retrieves all games from the WebSocketService. + /// + /// + /// An IEnumerable of Game objects representing all the games. + /// [HttpGet("[action]")] public IEnumerable All() { @@ -24,6 +33,12 @@ public class GameController(ILogger logger, IGameService webSock return webSocketService.Games; } + /// + /// Adds a player to a game. + /// + /// The unique identifier of the game. + /// The player to be joined. + /// An IActionResult representing the result of the operation. [HttpPost("[action]/{gameId:guid}")] public IActionResult Join(Guid gameId, [FromBody] Player player) // TODO what if player is in a game already? { @@ -43,6 +58,15 @@ public class GameController(ILogger logger, IGameService webSock } } + /// + /// Checks if a game with the specified ID exists. + /// + /// The ID of the game to check. + /// + /// Returns an representing the result of the operation. + /// If a game with the specified ID exists, returns an . + /// If a game with the specified ID doesn't exist, returns a . + /// [HttpGet("[action]/{gameId:guid}")] public IActionResult Exists(Guid gameId) { @@ -50,6 +74,16 @@ public class GameController(ILogger logger, IGameService webSock return webSocketService.Games.Any(game => game.Id == gameId) ? Ok() : NotFound(); } + /// + /// Creates a new game and adds the specified player to it. + /// + /// The data required to create the game. + /// + /// Returns an representing the result of the operation. + /// If the game is successfully created, returns a with the game details and a location + /// URL. + /// If there is an error during creation, returns a with the error message. + /// [HttpPost("[action]")] public IActionResult Create([FromBody] CreateGameData data) { @@ -71,6 +105,14 @@ public class GameController(ILogger logger, IGameService webSock return base.Echo(); } + /// + /// Runs the given WebSocketReceiveResult and byte array data to perform an action. + /// + /// The WebSocketReceiveResult object containing information about the received data. + /// The byte array data received from the WebSocket. + /// + /// Returns an ArraySegment object representing the action response in byte array format. + /// protected override ArraySegment Run(WebSocketReceiveResult result, byte[] data) { var stringResult = data.GetString(result.Count); @@ -91,12 +133,16 @@ public class GameController(ILogger logger, IGameService webSock return action.ToArraySegment(); } - protected override async void Send(ArraySegment segment) + /// + /// Sends the specified data segment. + /// + /// The data segment to send. + protected override void Send(ArraySegment segment) { if (actionService.Game is not null) actionService.SendToAll(segment); else if (WebSocket is not null) - await webSocketService.Send(WebSocket, segment); + webSocketService.Send(WebSocket, segment); } protected override ArraySegment? Disconnect() => @@ -105,6 +151,10 @@ public class GameController(ILogger logger, IGameService webSock protected override void SendDisconnectMessage(ArraySegment segment) => actionService.SendToAll(segment); + /// + /// Performs the specified action based on the given message. + /// + /// The action message containing the action to be performed. public void DoAction(ActionMessage message) => message.Data = message.Action switch { diff --git a/pac-man-board-game/Controllers/GenericController.cs b/pac-man-board-game/Controllers/GenericController.cs index 1a3f865..502e02f 100644 --- a/pac-man-board-game/Controllers/GenericController.cs +++ b/pac-man-board-game/Controllers/GenericController.cs @@ -4,13 +4,33 @@ using pacMan.Services; namespace pacMan.Controllers; +/// +/// Represents a generic controller for handling WebSocket connections. +/// public abstract class GenericController(ILogger logger, IWebSocketService webSocketService) : ControllerBase { + /// + /// Buffer size used for processing data. + /// private const int BufferSize = 1024 * 4; + protected readonly ILogger Logger = logger; protected WebSocket? WebSocket; + /// + /// Establishes a WebSocket connection with the client. + /// + /// + /// This method checks if the HTTP request is a WebSocket request. If it is, it accepts the WebSocket connection, logs + /// the connection establishment, and sets the WebSocket property to + /// the accepted WebSocket instance. + /// After the connection is established, the method calls the Echo method to start echoing messages. + /// If the request is not a WebSocket request, it sets the HTTP response status code to 400 (BadRequest). + /// + /// + /// The task representing the asynchronous operation. + /// public virtual async Task Connect() { if (HttpContext.WebSockets.IsWebSocketRequest) @@ -26,6 +46,11 @@ public abstract class GenericController(ILogger logger, IWebS } } + /// + /// An asynchronous method that reads data from the WebSocket connection, + /// processes it, and sends back the processed data. + /// + /// A Task representing the asynchronous operation. protected virtual async Task Echo() { if (WebSocket is null) return; @@ -56,10 +81,15 @@ public abstract class GenericController(ILogger logger, IWebS } } - protected virtual async void Send(ArraySegment segment) + /// + /// Sends the specified byte segment using the WebSocket connection. + /// If the WebSocket connection is null, the method does nothing. + /// + /// The byte segment to send. + protected virtual void Send(ArraySegment segment) { if (WebSocket is null) return; - await webSocketService.Send(WebSocket, segment); + webSocketService.Send(WebSocket, segment); } protected abstract ArraySegment Run(WebSocketReceiveResult result, byte[] data); diff --git a/pac-man-board-game/Controllers/PlayerController.cs b/pac-man-board-game/Controllers/PlayerController.cs index dd8558a..8ed37cc 100644 --- a/pac-man-board-game/Controllers/PlayerController.cs +++ b/pac-man-board-game/Controllers/PlayerController.cs @@ -9,6 +9,11 @@ namespace pacMan.Controllers; [Route("api/[controller]/[action]")] public class PlayerController(UserService userService) : ControllerBase { + /// + /// Logs in a user. + /// + /// The user object containing the username and password. + /// Returns an IActionResult indicating the login result. [HttpPost] public async Task Login([FromBody] User user) { @@ -18,5 +23,5 @@ public class PlayerController(UserService userService) : ControllerBase } [HttpPost] - public async Task Register([FromBody] User user) => throw new NotSupportedException(); + public Task Register([FromBody] User user) => throw new NotSupportedException(); } diff --git a/pac-man-board-game/GameStuff/Actions.cs b/pac-man-board-game/GameStuff/Actions.cs index acfd6a3..ea8fb12 100644 --- a/pac-man-board-game/GameStuff/Actions.cs +++ b/pac-man-board-game/GameStuff/Actions.cs @@ -3,6 +3,9 @@ using System.Text.Json.Serialization; namespace pacMan.GameStuff; +/// +/// Represents various actions that can be performed in a game. +/// public enum GameAction { Error, @@ -14,12 +17,22 @@ public enum GameAction Disconnect } +/// +/// Represents an action message with optional data of type . +/// Every Action may have a different type of data, or no data at all. +/// +/// The type of the data. public class ActionMessage { [JsonPropertyName("action")] public GameAction Action { get; init; } [JsonPropertyName("data")] public T? Data { get; set; } + /// + /// Parses a JSON string into an ActionMessage object. With dynamic data. + /// + /// The JSON string to deserialize. + /// An ActionMessage object populated with the deserialized data. public static ActionMessage FromJson(string json) => JsonSerializer.Deserialize(json)!; } diff --git a/pac-man-board-game/GameStuff/Character.cs b/pac-man-board-game/GameStuff/Character.cs index 66211e2..493391e 100644 --- a/pac-man-board-game/GameStuff/Character.cs +++ b/pac-man-board-game/GameStuff/Character.cs @@ -31,9 +31,12 @@ public class Character : IEquatable return obj.GetType() == GetType() && Equals((Character)obj); } - public override int GetHashCode() => HashCode.Combine(Colour, Position, IsEatable, SpawnPosition, (int?)Type); + public override int GetHashCode() => HashCode.Combine(Colour, Type); } +/// +/// Represents the types of characters in a game. +/// public enum CharacterType { PacMan, diff --git a/pac-man-board-game/GameStuff/Items/Dice.cs b/pac-man-board-game/GameStuff/Items/Dice.cs index 9c593b5..5275ec9 100644 --- a/pac-man-board-game/GameStuff/Items/Dice.cs +++ b/pac-man-board-game/GameStuff/Items/Dice.cs @@ -6,7 +6,14 @@ public class Dice { private readonly Random _random = new(); - [JsonInclude] public int Value { get; private set; } + /// + /// Represents the value of the previous roll. + /// + [JsonInclude] + public int Value { get; private set; } + /// + /// Rolls a dice by generating a random number between 1 and 6 and assigns it to the 'Value' property of the dice. + /// public void Roll() => Value = _random.Next(1, 7); } diff --git a/pac-man-board-game/GameStuff/Items/DiceCup.cs b/pac-man-board-game/GameStuff/Items/DiceCup.cs index 40bdf99..e8e6cea 100644 --- a/pac-man-board-game/GameStuff/Items/DiceCup.cs +++ b/pac-man-board-game/GameStuff/Items/DiceCup.cs @@ -2,15 +2,24 @@ using System.Text.Json.Serialization; namespace pacMan.GameStuff.Items; +/// +/// Represents a cup containing multiple dice. +/// public class DiceCup { - private readonly List _dices = new() - { - new Dice(), - new Dice() - }; + private readonly List _dices = [new Dice(), new Dice()]; - [JsonInclude] public List Values => _dices.Select(dice => dice.Value).ToList(); + /// + /// Gets a list of integer values representing the values of the dices. + /// + /// + /// A list of integer values representing the values of the dices. + /// + [JsonInclude] + public List Values => _dices.Select(dice => dice.Value).ToList(); + /// + /// Rolls all the dice in the list. + /// public void Roll() => _dices.ForEach(dice => dice.Roll()); } diff --git a/pac-man-board-game/GameStuff/Items/Player.cs b/pac-man-board-game/GameStuff/Items/Player.cs index bbfaaa1..fcaf389 100644 --- a/pac-man-board-game/GameStuff/Items/Player.cs +++ b/pac-man-board-game/GameStuff/Items/Player.cs @@ -3,6 +3,9 @@ using DAL.Database.Models; namespace pacMan.GameStuff.Items; +/// +/// Represents the various states of a 'Player'. +/// public enum State { WaitingForPlayers, @@ -11,6 +14,9 @@ public enum State Disconnected } +/// +/// Represents a player in the game. +/// public class Player : IEquatable, ICloneable { [JsonPropertyName("username")] public required string Username { get; init; } diff --git a/pac-man-board-game/GameStuff/Positions.cs b/pac-man-board-game/GameStuff/Positions.cs index accff30..94c8a76 100644 --- a/pac-man-board-game/GameStuff/Positions.cs +++ b/pac-man-board-game/GameStuff/Positions.cs @@ -2,6 +2,9 @@ using System.Text.Json.Serialization; namespace pacMan.GameStuff; +/// +/// Represents a move path consisting of a sequence of positions and a target end position with a specified direction. +/// public class MovePath : IEquatable { [JsonInclude] @@ -19,6 +22,11 @@ public class MovePath : IEquatable return Equals(Path, other.Path) && End.Equals(other.End) && Direction == other.Direction; } + /// + /// Converts a DirectionalPosition object to a MovePath object. + /// + /// The DirectionalPosition object to convert. + /// A MovePath object with the same End and Direction as the DirectionalPosition object. public static implicit operator MovePath(DirectionalPosition path) => new() { @@ -33,9 +41,12 @@ public class MovePath : IEquatable return obj.GetType() == GetType() && Equals((MovePath)obj); } - public override int GetHashCode() => HashCode.Combine(Path, End, (int)Direction); + public override int GetHashCode() => HashCode.Combine(End, (int)Direction); } +/// +/// Represents a position with x and y coordinates. +/// public class Position : IEquatable { [JsonPropertyName("x")] public int X { get; init; } @@ -59,6 +70,9 @@ public class Position : IEquatable public override int GetHashCode() => HashCode.Combine(X, Y); } +/// +/// Enum representing the possible directions: Left, Up, Right, and Down. +/// public enum Direction { Left, @@ -67,6 +81,9 @@ public enum Direction Down } +/// +/// Represents a directional position with a coordinate and a direction. +/// public class DirectionalPosition : IEquatable { [JsonPropertyName("at")] public required Position At { get; init; } @@ -80,6 +97,11 @@ public class DirectionalPosition : IEquatable return At.Equals(other.At) && Direction == other.Direction; } + /// + /// Converts a MovePath object to a DirectionalPosition object. + /// + /// The MovePath object to convert. + /// A DirectionalPosition object representing the converted MovePath object. public static explicit operator DirectionalPosition(MovePath path) => new() { diff --git a/pac-man-board-game/GameStuff/Rules.cs b/pac-man-board-game/GameStuff/Rules.cs index 5052cc4..4c5e5a2 100644 --- a/pac-man-board-game/GameStuff/Rules.cs +++ b/pac-man-board-game/GameStuff/Rules.cs @@ -1,5 +1,8 @@ namespace pacMan.GameStuff; +/// +/// The Rules class holds constant values related to the game rules. +/// public static class Rules { public const int MinPlayers = 2; diff --git a/pac-man-board-game/Services/ActionService.cs b/pac-man-board-game/Services/ActionService.cs index 4ec6b5a..9a3d193 100644 --- a/pac-man-board-game/Services/ActionService.cs +++ b/pac-man-board-game/Services/ActionService.cs @@ -21,6 +21,9 @@ public interface IActionService List? Disconnect(); } +/// +/// Provides various actions that can be performed in a game +/// public class ActionService(ILogger logger, IGameService gameService) : IActionService { public WebSocket WebSocket { private get; set; } = null!; @@ -29,15 +32,24 @@ public class ActionService(ILogger logger, IGameService gameService) : IActionSe public Player? Player { get; set; } + /// + /// Rolls the dice and returns the result. If the game is null, an empty list is returned. + /// + /// A list of integers representing the values rolled on the dice. public List RollDice() { Game?.DiceCup.Roll(); - var rolls = Game?.DiceCup.Values ?? new List(); + var rolls = Game?.DiceCup.Values ?? []; logger.LogInformation("Rolled [{}]", string.Join(", ", rolls)); return rolls; } + /// + /// Handles the movement of the character based on the provided JSON element. + /// + /// The JSON element containing the data to move the character. + /// The MovePlayerData object representing the updated character movement information. public MovePlayerData HandleMoveCharacter(JsonElement? jsonElement) { var data = jsonElement?.Deserialize() ?? throw new NullReferenceException("Data is null"); @@ -50,6 +62,14 @@ public class ActionService(ILogger logger, IGameService gameService) : IActionSe return data; } + /// + /// Finds a game based on the given JSON element. + /// + /// The JSON data containing the username and gameId. + /// The list of players in the found game. + /// Thrown when the JSON data is null. + /// Thrown when the game with the given gameId does not exist. + /// Thrown when the player with the given username is not found in the game. public List FindGame(JsonElement? jsonElement) { var (username, gameId) = @@ -69,6 +89,12 @@ public class ActionService(ILogger logger, IGameService gameService) : IActionSe return Game.Players; } + /// + /// Prepares the game and returns relevant data. + /// + /// Thrown when the player is not found. + /// Thrown when the game is not found. + /// A object containing information about game readiness. public ReadyData Ready() { if (Player is null) @@ -84,8 +110,22 @@ public class ActionService(ILogger logger, IGameService gameService) : IActionSe return new ReadyData { AllReady = allReady, Players = players }; } + /// + /// Finds the next player in the game. + /// + /// + /// The username of the next player in the game, if available. + /// + /// + /// Thrown if the game is not found. + /// public string FindNextPlayer() => Game?.NextPlayer().Username ?? throw new GameNotFoundException(); + /// + /// Removes the player from the game. + /// + /// Throws if the game or player is null. + /// A list of remaining players in the game. public List LeaveGame() { if (Game is null) throw new NullReferenceException("Game is null"); @@ -94,6 +134,13 @@ public class ActionService(ILogger logger, IGameService gameService) : IActionSe return Game.Players; } + /// + /// Disconnects the player from the game. + /// + /// + /// Returns the list of players in the game after disconnecting the player. + /// Returns null if the player is already disconnected or is not connected to a game. + /// public List? Disconnect() { if (Player is null) return null; @@ -102,7 +149,16 @@ public class ActionService(ILogger logger, IGameService gameService) : IActionSe return Game?.Players; } + /// + /// Sends a given byte segment to all players in the game. + /// + /// The byte segment to send. public void SendToAll(ArraySegment segment) => Game?.SendToAll(segment); - private async Task SendSegment(ArraySegment segment) => await gameService.Send(WebSocket, segment); + /// + /// Sends an array segment of bytes through the WebSocket connection. + /// + /// The array segment of bytes to send. + /// A task that represents the asynchronous send operation. + private Task SendSegment(ArraySegment segment) => gameService.Send(WebSocket, segment); } diff --git a/pac-man-board-game/Services/Game.cs b/pac-man-board-game/Services/Game.cs index f971448..c09ab9a 100644 --- a/pac-man-board-game/Services/Game.cs +++ b/pac-man-board-game/Services/Game.cs @@ -5,14 +5,21 @@ using pacMan.GameStuff.Items; namespace pacMan.Services; +/// +/// Represents a game instance. +/// public class Game(Queue spawns) { private readonly Random _random = new(); private int _currentPlayerIndex; - private List _players = new(); + private List _players = []; [JsonInclude] public Guid Id { get; } = Guid.NewGuid(); + /// + /// Gets or sets the list of players. + /// When setting, the mutable values of the players are updated instead of replacing the list. + /// [JsonIgnore] public List Players { @@ -32,17 +39,36 @@ public class Game(Queue spawns) } } - [JsonIgnore] public List Ghosts { get; set; } = new(); // TODO include + [JsonIgnore] public List Ghosts { get; set; } = []; // TODO include - [JsonIgnore] private Queue Spawns { get; } = spawns; + /// + /// The spawn locations on the map. + /// + /// + /// A Queue of DirectionalPositions representing the spawn locations on the map. + /// + [JsonIgnore] + private Queue Spawns { get; } = spawns; [JsonIgnore] public DiceCup DiceCup { get; } = new(); // TODO include [JsonInclude] public int Count => Players.Count; // TODO edge-case when game has started but all players have disconnected, Disconnected property? - [JsonInclude] public bool IsGameStarted => Count > 0 && Players.Any(player => player.State is State.InGame); + /// + /// Whether or not the game has started. + /// + /// + /// The game is considered started if the count is greater than zero and at least one player is in the "InGame" state. + /// + [JsonInclude] + public bool IsGameStarted => Count > 0 && Players.Any(player => player.State is State.InGame); + /// + /// Gets the next player in the game. + /// + /// The next player. + /// Thrown when there are no players in the game. public Player NextPlayer() { try @@ -59,8 +85,22 @@ public class Game(Queue spawns) public void Shuffle() => Players.Sort((_, _) => _random.Next(-1, 2)); + /// + /// An event that is invoked when a message is to be sent to all connections. + /// Each player in the game should be listening to this event. + /// + /// + /// The event handler is of type which accepts an of + /// bytes and returns a . + /// This event is typically used to perform some action when something happens. + /// public event Func, Task>? Connections; + /// + /// Adds a player to the game. + /// + /// The player to be added. + /// Thrown when the game is already full or has already started. public void AddPlayer(Player player) { if (Players.Count >= Rules.MaxPlayers) @@ -84,6 +124,10 @@ public class Game(Queue spawns) return removedPlayer; } + /// + /// Sets the spawn position and current position of the specified player's PacMan character. + /// + /// The player whose PacMan character's spawn and current positions will be set. private void SetSpawn(Player player) { if (player.PacMan.SpawnPosition is not null) return; @@ -92,8 +136,17 @@ public class Game(Queue spawns) player.PacMan.Position = spawn; } + /// + /// Sends the specified byte segment to all connected clients. + /// + /// The byte segment to send. public void SendToAll(ArraySegment segment) => Connections?.Invoke(segment); + /// + /// Sets the state of the player with the specified username to Ready. + /// + /// The username of the player. + /// An enumerable collection of Player objects. public IEnumerable SetReady(string username) { var player = Players.FirstOrDefault(p => p.Username == username); @@ -103,6 +156,12 @@ public class Game(Queue spawns) return Players; } + /// + /// Sets all players to the "InGame" state if they are currently in the "Ready" state. + /// + /// + /// Returns true if all players were successfully set to the "InGame" state, false otherwise. + /// public bool SetAllInGame() { if (Players.Any(player => player.State is not State.Ready)) return false; @@ -111,6 +170,11 @@ public class Game(Queue spawns) return true; } + /// + /// Finds a player by their username. + /// + /// The username of the player to find. + /// The found Player object if a player with the given username is found; otherwise, null. public Player? FindPlayerByUsername(string username) => Players.FirstOrDefault(player => player.Username == username); } diff --git a/pac-man-board-game/Services/GameService.cs b/pac-man-board-game/Services/GameService.cs index c4c6cf5..2ca4f53 100644 --- a/pac-man-board-game/Services/GameService.cs +++ b/pac-man-board-game/Services/GameService.cs @@ -64,13 +64,20 @@ public class GameService(ILogger logger) : WebSocketService(logger), IGameServic return game; } - public Game? FindGameById(Guid id) - { - return Games.FirstOrDefault(game => game.Id == id); - } + /// + /// Finds a game by its ID. + /// + /// The ID of the game. + /// The game with the specified ID, or null if no game was found. + public Game? FindGameById(Guid id) => Games.FirstOrDefault(game => game.Id == id); - public Game? FindGameByUsername(string username) - { - return Games.FirstOrDefault(game => game.Players.Exists(player => player.Username == username)); - } + /// + /// Finds a game by the given username. + /// + /// The username to search for. + /// + /// The found game, if any. Returns null if no game is found. + /// + public Game? FindGameByUsername(string username) => + Games.FirstOrDefault(game => game.Players.Exists(player => player.Username == username)); } diff --git a/pac-man-board-game/Services/WebSocketService.cs b/pac-man-board-game/Services/WebSocketService.cs index e4a415c..ecbf8ef 100644 --- a/pac-man-board-game/Services/WebSocketService.cs +++ b/pac-man-board-game/Services/WebSocketService.cs @@ -10,8 +10,24 @@ public interface IWebSocketService Task Close(WebSocket webSocket, WebSocketCloseStatus closeStatus, string? closeStatusDescription); } +/// +/// WebSocketService class provides methods to send, receive and close a WebSocket connection. +/// public class WebSocketService(ILogger logger) : IWebSocketService { + /// + /// Sends the specified byte array as a text message through the WebSocket connection. + /// + /// The WebSocket connection. + /// The byte array to send. + /// + /// A task representing the asynchronous operation of sending the message. + /// + /// + /// This method sends the specified byte array as a text message through the WebSocket connection. + /// It uses the WebSocket.SendAsync method to send the message asynchronously. + /// After sending the message, it logs a debug message using the logger provided. + /// public async Task Send(WebSocket webSocket, ArraySegment segment) { await webSocket.SendAsync( @@ -23,6 +39,15 @@ public class WebSocketService(ILogger logger) : IWebSocketService logger.LogDebug("Message sent through WebSocket"); } + /// + /// Receives data from a websocket and logs a debug message. + /// + /// The websocket to receive data from. + /// The buffer to store the received data. + /// + /// A task representing the asynchronous operation. The result contains the + /// which contains information about the received data. + /// public async Task Receive(WebSocket webSocket, byte[] buffer) { var result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); @@ -32,6 +57,13 @@ public class WebSocketService(ILogger logger) : IWebSocketService return result; } + /// + /// Closes the WebSocket connection with the specified close status and description. + /// + /// The WebSocket connection to close. + /// The status code indicating the reason for the close. + /// The optional description explaining the reason for the close. + /// A task representing the asynchronous operation. public async Task Close(WebSocket webSocket, WebSocketCloseStatus closeStatus, string? closeStatusDescription) { await webSocket.CloseAsync( diff --git a/pac-man-board-game/Utils/Extensions.cs b/pac-man-board-game/Utils/Extensions.cs index fae9f58..236ab0c 100644 --- a/pac-man-board-game/Utils/Extensions.cs +++ b/pac-man-board-game/Utils/Extensions.cs @@ -6,6 +6,12 @@ namespace pacMan.Utils; public static partial class Extensions { + /// + /// Converts the specified byte array into a string using UTF-8 encoding and then removes any invalid characters. + /// + /// The byte array to convert. + /// The number of bytes to decode. + /// The decoded string without any invalid characters. public static string GetString(this byte[] bytes, int length) { var s = Encoding.UTF8.GetString(bytes, 0, length); @@ -13,6 +19,11 @@ public static partial class Extensions return InvalidCharacters().Replace(s, string.Empty); } + /// + /// Converts an object to an of bytes. + /// + /// The object to convert. + /// An of bytes representing the serialized object in UTF-8 encoding. public static ArraySegment ToArraySegment(this object obj) { var json = JsonSerializer.Serialize(obj); @@ -20,6 +31,10 @@ public static partial class Extensions return new ArraySegment(bytes, 0, json.Length); } + /// + /// Retrieves a regular expression pattern that matches invalid characters. + /// + /// A regular expression pattern for matching invalid characters. [GeneratedRegex("\\p{C}+")] private static partial Regex InvalidCharacters(); } diff --git a/pac-man-board-game/pac-man-board-game.csproj b/pac-man-board-game/pac-man-board-game.csproj index dd6b581..e5ed925 100644 --- a/pac-man-board-game/pac-man-board-game.csproj +++ b/pac-man-board-game/pac-man-board-game.csproj @@ -18,7 +18,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive