diff --git a/API/API.csproj b/API/API.csproj index 7330b1b..e51252d 100644 --- a/API/API.csproj +++ b/API/API.csproj @@ -12,9 +12,7 @@ - - diff --git a/API/Controllers/GameController.cs b/API/Controllers/GameHubSocket.cs similarity index 79% rename from API/Controllers/GameController.cs rename to API/Controllers/GameHubSocket.cs index 971d3e4..eede8c9 100644 --- a/API/Controllers/GameController.cs +++ b/API/Controllers/GameHubSocket.cs @@ -2,9 +2,9 @@ using Microsoft.AspNetCore.SignalR; namespace API.Controllers; -public class GameController : Hub +public class GameHubSocket : Hub { - public async Task JoinGame(string gameId, string playerName) + public async Task CreateGame(string playerName, ) { await Groups.AddToGroupAsync(Context.ConnectionId, gameId); await Clients.Group(gameId).SendAsync("PlayerJoined", new @@ -13,4 +13,9 @@ public class GameController : Hub PlayerName = playerName }); } + + public async Task JoinGame(string gameId, string playerName) + { + + } } \ No newline at end of file diff --git a/API/Models/DataClasses/SixDigitInt.cs b/API/Models/DataClasses/SixDigitInt.cs new file mode 100644 index 0000000..0ee9112 --- /dev/null +++ b/API/Models/DataClasses/SixDigitInt.cs @@ -0,0 +1,20 @@ +namespace API.Models.DataClasses +{ + public readonly record struct SixDigitInt + { + public int Value { get; } + + public SixDigitInt(int value) + { + if (value < 0 || value > 999999) + throw new ArgumentOutOfRangeException(nameof(value), + "Wert muss zwischen 0 und 999999 liegen."); + + Value = value; + } + + public override string ToString() => Value.ToString("D6"); + + public static implicit operator int(SixDigitInt v) => v.Value; + } +} diff --git a/API/Models/Game/Game.cs b/API/Models/Game/Game.cs new file mode 100644 index 0000000..7f3359a --- /dev/null +++ b/API/Models/Game/Game.cs @@ -0,0 +1,26 @@ +using API.Models.DataClasses; + +namespace API.Models.Game +{ + public enum GameState + { + Lobby, + Running, + Ended + } + + public class Game + { + public string Id { get; init; } = Guid.NewGuid().ToString(); + public SixDigitInt GameCode { get; } + public string?[] PlayerConnectionIds { get; set; } = new string?[2]; + public GameState State { get; private set; } = GameState.Lobby; + public GameField Field { get; } + + public Game(Coordinates gFs, SixDigitInt gameCode) + { + Field = new GameField(gFs); + GameCode = gameCode; + } + } +} diff --git a/API/Models/Game/GameField.cs b/API/Models/Game/GameField.cs new file mode 100644 index 0000000..98377c3 --- /dev/null +++ b/API/Models/Game/GameField.cs @@ -0,0 +1,86 @@ +namespace API.Models.Game +{ + public class Coordinates + { + public int X; + public int Y; + } + + public enum PlaceResult + { + OutOfGameField, + NotAllowedPlayer, + OccupiedRed, + OccupiedYellow, + InvalidFieldValue, + Placed + } + + public enum FieldState + { + OutOfGameField, + Empty, + OccupiedRed, + OccupiedYellow, + InvalidFieldValue + } + + public class GameField(Coordinates gFs) + { + public int[,] CurrentField { get; } = new int[gFs.Y, gFs.X]; + public int[,] BackupField { get; } = new int[gFs.Y, gFs.X]; + + public PlaceResult Place(Coordinates coordinates, int player) + { + if (coordinates.X < 0 || coordinates.X >= CurrentField.GetLength(1) || + coordinates.Y < 0 || coordinates.Y >= CurrentField.GetLength(0)) + { + return PlaceResult.OutOfGameField; + } + + if (player != 1 && player != 2) + return PlaceResult.NotAllowedPlayer; + + int currentValue = CurrentField[coordinates.Y, coordinates.X]; + + if (currentValue != 0) + { + return currentValue switch + { + 1 => PlaceResult.OccupiedRed, + 2 => PlaceResult.OccupiedYellow, + _ => PlaceResult.InvalidFieldValue + }; + } + + CreateSave(); + CurrentField[coordinates.Y, coordinates.X] = player; + + return PlaceResult.Placed; + } + + public FieldState CheckField(Coordinates coordinates) + { + if (coordinates.X < 0 || coordinates.X >= CurrentField.GetLength(1) || + coordinates.Y < 0 || coordinates.Y >= CurrentField.GetLength(0)) + { + return FieldState.OutOfGameField; + } + + int currentValue = CurrentField[coordinates.Y, coordinates.X]; + + return currentValue switch + { + 0 => FieldState.Empty, + 1 => FieldState.OccupiedRed, + 2 => FieldState.OccupiedYellow, + _ => FieldState.InvalidFieldValue + }; + } + + private void CreateSave() + { + Array.Copy(CurrentField, BackupField, CurrentField.Length); + } + } +} \ No newline at end of file diff --git a/API/Program.cs b/API/Program.cs index d65a162..0e8ac50 100644 --- a/API/Program.cs +++ b/API/Program.cs @@ -1,3 +1,5 @@ +using API.Services.GameManager; + var builder = WebApplication.CreateBuilder(args); // Add services to the container. @@ -7,6 +9,8 @@ builder.Services.AddSignalR(); builder.Services.AddOpenApi(); builder.Services.AddSwaggerGen(); +builder.Services.AddSingleton(); + var app = builder.Build(); // Configure the HTTP request pipeline. @@ -24,9 +28,8 @@ app.UseStaticFiles(); app.UseAuthorization(); app.MapControllers(); -app.MapHub("/api/game"); +app.MapHub("/api/gamehub"); -// SPA fallback: serve index.html for routes not matched by API or static files app.MapFallbackToFile("index.html"); app.Run(); diff --git a/API/Services/GameManager/GameManager.cs b/API/Services/GameManager/GameManager.cs new file mode 100644 index 0000000..2813ee5 --- /dev/null +++ b/API/Services/GameManager/GameManager.cs @@ -0,0 +1,20 @@ +namespace API.Services.GameManager +{ + public class GameManager : IGameManager + { + public int CreateGame(string playerName) + { + throw new NotImplementedException(); + } + + public bool JoinGame(string playerName, int gameCode) + { + throw new NotImplementedException(); + } + + private int GenerateNonExistingGameCode() + { + return 0; + } + } +} diff --git a/API/Services/GameManager/IGameManager.cs b/API/Services/GameManager/IGameManager.cs new file mode 100644 index 0000000..dbb18d7 --- /dev/null +++ b/API/Services/GameManager/IGameManager.cs @@ -0,0 +1,8 @@ +namespace API.Services.GameManager +{ + public interface IGameManager + { + public int CreateGame(string playerName); + public bool JoinGame(string playerName, int gameCode); + } +} diff --git a/GUI/package-lock.json b/GUI/package-lock.json index 9d310fa..4c13612 100644 --- a/GUI/package-lock.json +++ b/GUI/package-lock.json @@ -62,7 +62,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1386,7 +1385,6 @@ "integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -1850,7 +1848,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -2977,7 +2974,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3085,7 +3081,6 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -3271,7 +3266,6 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.28.tgz", "integrity": "sha512-BRdrNfeoccSoIZeIhyPBfvWSLFP4q8J3u8Ju8Ug5vu3LdD+yTM13Sg4sKtljxozbnuMu1NB1X5HBHRYUzFocKg==", "license": "MIT", - "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.28", "@vue/compiler-sfc": "3.5.28", diff --git a/GUI/src/scripts/logic/localMode/LocalGame.ts b/GUI/src/scripts/logic/localMode/LocalGame.ts index 33b0b2e..f45fcb2 100644 --- a/GUI/src/scripts/logic/localMode/LocalGame.ts +++ b/GUI/src/scripts/logic/localMode/LocalGame.ts @@ -1,10 +1,11 @@ import type { FieldSize } from '@/scripts/interfaces/FieldSize.ts' +import buildFieldArray from '@/scripts/utils/BuildFieldArray' class LocalGame { public field: number[][] constructor(fieldSize: FieldSize) { - this.field = Array.from({ length: fieldSize.y }, () => Array(fieldSize.x).fill(0)) + this.field = buildFieldArray(fieldSize); } } diff --git a/GUI/src/scripts/logic/signalR/GameConnection.ts b/GUI/src/scripts/logic/signalR/GameConnection.ts index 47af739..98a6d2a 100644 --- a/GUI/src/scripts/logic/signalR/GameConnection.ts +++ b/GUI/src/scripts/logic/signalR/GameConnection.ts @@ -2,9 +2,9 @@ import * as signalR from '@microsoft/signalr'; class GameConnection{ public connection: signalR.HubConnection - constructor(gameSession: string) { + constructor(playerName: string, joinCode: number | undefined) { this.connection = new signalR.HubConnectionBuilder() - .withUrl(`/gameHub?gameSession=${gameSession}`) + .withUrl("/api/gamehub") .withAutomaticReconnect() .build(); } diff --git a/GUI/src/scripts/utils/BuildFieldArray.ts b/GUI/src/scripts/utils/BuildFieldArray.ts new file mode 100644 index 0000000..2db5728 --- /dev/null +++ b/GUI/src/scripts/utils/BuildFieldArray.ts @@ -0,0 +1,7 @@ +import type { FieldSize } from "../interfaces/FieldSize"; + +function buildFieldArray(fieldSize: FieldSize): number[][]{ + return Array.from({ length: fieldSize.y }, () => Array(fieldSize.x).fill(0)); +} + +export default buildFieldArray; \ No newline at end of file