Added Backend Classes and FUnctions for an working Game. But lacking Implementations

This commit is contained in:
jhim
2026-02-27 11:16:21 +01:00
committed by Jonas
parent 120c671dce
commit 5db7ac1676
12 changed files with 183 additions and 15 deletions
-2
View File
@@ -12,9 +12,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Models\" />
<Folder Include="Repository\" /> <Folder Include="Repository\" />
<Folder Include="Services\" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -2,9 +2,9 @@ using Microsoft.AspNetCore.SignalR;
namespace API.Controllers; 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 Groups.AddToGroupAsync(Context.ConnectionId, gameId);
await Clients.Group(gameId).SendAsync("PlayerJoined", new await Clients.Group(gameId).SendAsync("PlayerJoined", new
@@ -13,4 +13,9 @@ public class GameController : Hub
PlayerName = playerName PlayerName = playerName
}); });
} }
public async Task JoinGame(string gameId, string playerName)
{
}
} }
+20
View File
@@ -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;
}
}
+26
View File
@@ -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;
}
}
}
+86
View File
@@ -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);
}
}
}
+5 -2
View File
@@ -1,3 +1,5 @@
using API.Services.GameManager;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
// Add services to the container. // Add services to the container.
@@ -7,6 +9,8 @@ builder.Services.AddSignalR();
builder.Services.AddOpenApi(); builder.Services.AddOpenApi();
builder.Services.AddSwaggerGen(); builder.Services.AddSwaggerGen();
builder.Services.AddSingleton<IGameManager, GameManager>();
var app = builder.Build(); var app = builder.Build();
// Configure the HTTP request pipeline. // Configure the HTTP request pipeline.
@@ -24,9 +28,8 @@ app.UseStaticFiles();
app.UseAuthorization(); app.UseAuthorization();
app.MapControllers(); app.MapControllers();
app.MapHub<API.Controllers.GameController>("/api/game"); app.MapHub<API.Controllers.GameHubSocket>("/api/gamehub");
// SPA fallback: serve index.html for routes not matched by API or static files
app.MapFallbackToFile("index.html"); app.MapFallbackToFile("index.html");
app.Run(); app.Run();
+20
View File
@@ -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;
}
}
}
+8
View File
@@ -0,0 +1,8 @@
namespace API.Services.GameManager
{
public interface IGameManager
{
public int CreateGame(string playerName);
public bool JoinGame(string playerName, int gameCode);
}
}
-6
View File
@@ -62,7 +62,6 @@
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.29.0", "@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0", "@babel/generator": "^7.29.0",
@@ -1386,7 +1385,6 @@
"integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==", "integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"undici-types": "~7.16.0" "undici-types": "~7.16.0"
} }
@@ -1850,7 +1848,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.9.0", "baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759", "caniuse-lite": "^1.0.30001759",
@@ -2977,7 +2974,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@@ -3085,7 +3081,6 @@
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"esbuild": "^0.27.0", "esbuild": "^0.27.0",
"fdir": "^6.5.0", "fdir": "^6.5.0",
@@ -3271,7 +3266,6 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.28.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.28.tgz",
"integrity": "sha512-BRdrNfeoccSoIZeIhyPBfvWSLFP4q8J3u8Ju8Ug5vu3LdD+yTM13Sg4sKtljxozbnuMu1NB1X5HBHRYUzFocKg==", "integrity": "sha512-BRdrNfeoccSoIZeIhyPBfvWSLFP4q8J3u8Ju8Ug5vu3LdD+yTM13Sg4sKtljxozbnuMu1NB1X5HBHRYUzFocKg==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.5.28", "@vue/compiler-dom": "3.5.28",
"@vue/compiler-sfc": "3.5.28", "@vue/compiler-sfc": "3.5.28",
+2 -1
View File
@@ -1,10 +1,11 @@
import type { FieldSize } from '@/scripts/interfaces/FieldSize.ts' import type { FieldSize } from '@/scripts/interfaces/FieldSize.ts'
import buildFieldArray from '@/scripts/utils/BuildFieldArray'
class LocalGame { class LocalGame {
public field: number[][] public field: number[][]
constructor(fieldSize: FieldSize) { constructor(fieldSize: FieldSize) {
this.field = Array.from({ length: fieldSize.y }, () => Array(fieldSize.x).fill(0)) this.field = buildFieldArray(fieldSize);
} }
} }
@@ -2,9 +2,9 @@ import * as signalR from '@microsoft/signalr';
class GameConnection{ class GameConnection{
public connection: signalR.HubConnection public connection: signalR.HubConnection
constructor(gameSession: string) { constructor(playerName: string, joinCode: number | undefined) {
this.connection = new signalR.HubConnectionBuilder() this.connection = new signalR.HubConnectionBuilder()
.withUrl(`/gameHub?gameSession=${gameSession}`) .withUrl("/api/gamehub")
.withAutomaticReconnect() .withAutomaticReconnect()
.build(); .build();
} }
+7
View File
@@ -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;