Update .gitignore to exclude TypeScript build info and add dist directory. Expand README with project overview, technology stack, prerequisites, and instructions for running and testing the application.
ci / server (push) Failing after 4m10s
ci / client (push) Successful in 17s

This commit is contained in:
Leonid Pershin
2026-08-18 11:11:48 +03:00
parent 84aafb0b69
commit e6739e7912
84 changed files with 5698 additions and 2 deletions
+88
View File
@@ -0,0 +1,88 @@
using System.Net.WebSockets;
using HSchool.Server.Game;
using HSchool.Server.Net;
using HSchool.Simulation;
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
builder.Services.AddProblemDetails();
builder.Services.AddOpenApi();
builder.Services
.AddOptions<SimulationOptions>()
.Bind(builder.Configuration.GetSection(SimulationOptions.SectionName))
.Validate(options => options.TickRate is > 0 and <= 120, "Simulation:TickRate must be between 1 and 120.")
.Validate(options => options.WorldWidth > 0 && options.WorldHeight > 0, "World size must be positive.")
.ValidateOnStart();
builder.Services.AddSingleton<GameCommandQueue>();
builder.Services.AddSingleton<ClientRegistry>();
builder.Services.AddSingleton<GameMetrics>();
builder.Services.AddSingleton<GameSocketHandler>();
builder.Services.AddSingleton<GameLoopService>();
builder.Services.AddHostedService(sp => sp.GetRequiredService<GameLoopService>());
builder.Services.AddOpenTelemetry().WithMetrics(metrics => metrics.AddMeter(GameMetrics.MeterName));
var app = builder.Build();
app.UseExceptionHandler();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
app.UseWebSockets(new WebSocketOptions
{
KeepAliveInterval = TimeSpan.FromSeconds(30),
});
var api = app.MapGroup("/api");
api.MapGet("/status", (GameLoopService loop, ClientRegistry clients) =>
{
var options = loop.Options;
return new GameStatusResponse(
loop.CurrentTick,
options.TickRate,
loop.PlayerCount,
clients.Count,
options.WorldWidth,
options.WorldHeight);
})
.WithName("GetGameStatus");
// The realtime channel: one binary frame per protocol message, see docs/protocol.md.
app.Map("/ws/game", async (HttpContext context, GameSocketHandler handler) =>
{
if (!context.WebSockets.IsWebSocketRequest)
{
context.Response.StatusCode = StatusCodes.Status400BadRequest;
await context.Response.WriteAsync("This endpoint expects a WebSocket upgrade.");
return;
}
using WebSocket socket = await context.WebSockets.AcceptWebSocketAsync();
await handler.HandleAsync(socket, context.RequestAborted);
});
app.MapDefaultEndpoints();
// In a published container the built client lands in wwwroot next to the server.
app.UseFileServer();
app.Run();
/// <summary>Snapshot of loop health for dashboards and integration tests.</summary>
internal sealed record GameStatusResponse(
uint Tick,
int TickRate,
int Players,
int Connections,
float WorldWidth,
float WorldHeight);
/// <summary>Exposed so <c>WebApplicationFactory</c>-style tests can reference the entry point.</summary>
public partial class Program;