Phase G: scaffold .NET 10 + SignalR backend (engine port + hub + auth)
- server/ monorepo: Hokm.Engine (C# port of TS engine+AI, validated by sim), Hokm.Server (SignalR GameHub, in-memory matchmaking/rooms, server-side turn timers + bot fill + disconnect handling, per-seat state broadcast), Hokm.Sim - JWT dev auth (OTP 1234 + email); CORS for the Next client; /hub/game - NuGet restored from mirrors (Soroush Nexus + Liara); NuGetAudit off - README + .NET .gitignore; static class Engine renamed Rules (namespace clash) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Hokm.Engine\Hokm.Engine.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,52 @@
|
||||
using Hokm.Engine;
|
||||
|
||||
// All-AI simulation to validate the C# engine port (mirrors scripts/sim.ts).
|
||||
var rng = new Random(12345);
|
||||
int N = 300;
|
||||
int totalRounds = 0;
|
||||
|
||||
for (int i = 0; i < N; i++)
|
||||
{
|
||||
var (rounds, tricks) = PlayMatch(rng);
|
||||
totalRounds += rounds;
|
||||
if (tricks <= 0) throw new Exception("no tricks played");
|
||||
}
|
||||
|
||||
Console.WriteLine($"OK: {N} matches completed. avg rounds/match = {(double)totalRounds / N:0.0}");
|
||||
|
||||
static (int rounds, int tricks) PlayMatch(Random rng)
|
||||
{
|
||||
var g = Rules.CreateInitial(new[] { "P0", "P1", "P2", "P3" }, 7);
|
||||
Rules.SelectHakem(g, rng);
|
||||
Rules.DealForTrump(g, rng);
|
||||
|
||||
int rounds = 0, tricks = 0, guard = 0;
|
||||
while (g.Phase != Phase.MatchOver)
|
||||
{
|
||||
if (++guard > 200000) throw new Exception("loop guard tripped");
|
||||
|
||||
switch (g.Phase)
|
||||
{
|
||||
case Phase.ChoosingTrump:
|
||||
Rules.ChooseTrump(g, Ai.ChooseTrump(g.Players[g.Hakem!.Value].Hand));
|
||||
break;
|
||||
case Phase.Playing:
|
||||
int seat = g.Turn!.Value;
|
||||
Rules.PlayCard(g, seat, Ai.ChooseCard(g, seat));
|
||||
break;
|
||||
case Phase.TrickComplete:
|
||||
tricks++;
|
||||
if (g.CurrentTrick.Count != 4) throw new Exception("trick not full");
|
||||
Rules.AdvanceAfterTrick(g, 2);
|
||||
break;
|
||||
case Phase.RoundOver:
|
||||
rounds++;
|
||||
if (g.RoundTricks[0] + g.RoundTricks[1] > 13) throw new Exception("too many tricks");
|
||||
Rules.StartNextRound(g, rng);
|
||||
break;
|
||||
default:
|
||||
throw new Exception("unexpected phase: " + g.Phase);
|
||||
}
|
||||
}
|
||||
return (rounds, tricks);
|
||||
}
|
||||
Reference in New Issue
Block a user